Merge pull request #84 from bcwaldon/proc-cmdline
feat(proc-cmdline): Parse /proc/cmdline for cloud-config-url
This commit is contained in:
		| @@ -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=<url>', using the cloud-config served by an HTTP GET to <url>", 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) | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
							
								
								
									
										66
									
								
								datasource/proc_cmdline.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								datasource/proc_cmdline.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										47
									
								
								datasource/proc_cmdline_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								datasource/proc_cmdline_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user