diff --git a/coreos-cloudinit.go b/coreos-cloudinit.go index 68e23e1..f376721 100644 --- a/coreos-cloudinit.go +++ b/coreos-cloudinit.go @@ -26,6 +26,9 @@ func main() { var url string flag.StringVar(&url, "from-url", "", "Download user-data from provided url") + var useProcCmdline bool + flag.BoolVar(&useProcCmdline, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=', using the cloud-config served by an HTTP GET to ", datasource.ProcCmdlineLocation, datasource.ProcCmdlineCloudConfigFlag)) + var workspace string flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data") @@ -39,8 +42,8 @@ func main() { os.Exit(0) } - if file != "" && url != "" { - fmt.Println("Provide one of --from-file or --from-url") + if file != "" && url != "" && !useProcCmdline { + fmt.Println("Provide one of --from-file, --from-url or --from-proc-cmdline") os.Exit(1) } @@ -49,8 +52,10 @@ func main() { ds = datasource.NewLocalFile(file) } else if url != "" { ds = datasource.NewMetadataService(url) + } else if useProcCmdline { + ds = datasource.NewProcCmdline() } else { - fmt.Println("Provide one of --from-file or --from-url") + fmt.Println("Provide one of --from-file, --from-url or --from-proc-cmdline") os.Exit(1) } diff --git a/datasource/datasource.go b/datasource/datasource.go index d78fa3b..f6175a9 100644 --- a/datasource/datasource.go +++ b/datasource/datasource.go @@ -1,6 +1,31 @@ package datasource +import ( + "io/ioutil" + "net/http" +) + type Datasource interface { Fetch() ([]byte, error) Type() string } + +func fetchURL(url string) ([]byte, error) { + client := http.Client{} + resp, err := client.Get(url) + if err != nil { + return []byte{}, err + } + defer resp.Body.Close() + + if resp.StatusCode / 100 != 2 { + return []byte{}, nil + } + + respBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + return respBytes, nil +} diff --git a/datasource/metadata_service.go b/datasource/metadata_service.go index 899c5a2..e1376b0 100644 --- a/datasource/metadata_service.go +++ b/datasource/metadata_service.go @@ -1,36 +1,15 @@ package datasource -import ( - "io/ioutil" - "net/http" -) - type metadataService struct { - url string - client http.Client + url string } func NewMetadataService(url string) *metadataService { - return &metadataService{url, http.Client{}} + return &metadataService{url} } func (ms *metadataService) Fetch() ([]byte, error) { - resp, err := ms.client.Get(ms.url) - if err != nil { - return []byte{}, err - } - defer resp.Body.Close() - - if resp.StatusCode / 100 != 2 { - return []byte{}, nil - } - - respBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - return respBytes, nil + return fetchURL(ms.url) } func (ms *metadataService) Type() string { diff --git a/datasource/proc_cmdline.go b/datasource/proc_cmdline.go new file mode 100644 index 0000000..4f38fcf --- /dev/null +++ b/datasource/proc_cmdline.go @@ -0,0 +1,66 @@ +package datasource + +import ( + "errors" + "io/ioutil" + "log" + "strings" +) + +const ( + ProcCmdlineLocation = "/proc/cmdline" + ProcCmdlineCloudConfigFlag = "cloud-config-url" +) + +type procCmdline struct{} + +func NewProcCmdline() *procCmdline { + return &procCmdline{} +} + +func (self *procCmdline) Fetch() ([]byte, error) { + cmdline, err := ioutil.ReadFile(ProcCmdlineLocation) + if err != nil { + return nil, err + } + + url, err := findCloudConfigURL(string(cmdline)) + if err != nil { + return nil, err + } + + cfg, err := fetchURL(url) + if err != nil { + return nil, err + } + + return cfg, nil +} + +func (self *procCmdline) Type() string { + return "proc-cmdline" +} + +func findCloudConfigURL(input string) (url string, err error) { + err = errors.New("cloud-config-url not found") + for _, token := range strings.Split(input, " ") { + parts := strings.SplitN(token, "=", 2) + + key := parts[0] + key = strings.Replace(key, "_", "-", -1) + + if key != "cloud-config-url" { + continue + } + + if len(parts) != 2 { + log.Printf("Found cloud-config-url in /proc/cmdline with no value, ignoring.") + continue + } + + url = parts[1] + err = nil + } + + return +} diff --git a/datasource/proc_cmdline_test.go b/datasource/proc_cmdline_test.go new file mode 100644 index 0000000..7f0f2d3 --- /dev/null +++ b/datasource/proc_cmdline_test.go @@ -0,0 +1,47 @@ +package datasource + +import ( + "testing" +) + +func TestParseCmdlineCloudConfigFound(t *testing.T) { + tests := []struct { + input string + expect string + }{ + { + "cloud-config-url=example.com", + "example.com", + }, + { + "cloud_config_url=example.com", + "example.com", + }, + { + "cloud-config-url cloud-config-url=example.com", + "example.com", + }, + { + "cloud-config-url= cloud-config-url=example.com", + "example.com", + }, + { + "cloud-config-url=one.example.com cloud-config-url=two.example.com", + "two.example.com", + }, + { + "foo=bar cloud-config-url=example.com ping=pong", + "example.com", + }, + } + + for i, tt := range tests { + output, err := findCloudConfigURL(tt.input) + if output != tt.expect { + t.Errorf("Test case %d failed: %s != %s", i, output, tt.expect) + } + if err != nil { + t.Errorf("Test case %d produced error: %v", i, err) + } + } +} diff --git a/test b/test index 9dae2cd..dd04610 100755 --- a/test +++ b/test @@ -4,7 +4,7 @@ echo "Building bin/coreos-cloudinit" . build echo "Running tests..." -for pkg in "./initialize ./system"; do +for pkg in "./initialize ./system ./datasource"; do go test -i $pkg go test -v $pkg done