feat(init): Add azure as a supported platform
Update initramfs to interact with azure endpoints for userdata. Signed-off-by: Brad Beam <brad.beam@talos-systems.com>
This commit is contained in:
parent
e9482a4041
commit
7adef1ea62
19
Makefile
19
Makefile
@ -300,3 +300,22 @@ push: gitmeta
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@-rm -rf build images vendor
|
||||
|
||||
.PHONY: talos-azure
|
||||
talos-azure:
|
||||
@docker run --rm -v /dev:/dev -v $(PWD)/build:/out \
|
||||
--privileged $(DOCKER_ARGS) \
|
||||
autonomy/installer:$(TAG) \
|
||||
install \
|
||||
-n disk \
|
||||
-r \
|
||||
-p azure \
|
||||
-u none \
|
||||
-e rootdelay=300
|
||||
@docker run --rm -v $(PWD)/build:/out $(DOCKER_ARGS) \
|
||||
--entrypoint qemu-img \
|
||||
autonomy/installer:$(TAG) \
|
||||
convert \
|
||||
-f raw \
|
||||
-o subformat=fixed,force_size \
|
||||
-O vpc /out/disk.raw /out/talos-azure.vhd
|
||||
|
78
internal/app/init/internal/platform/cloud/azure/azure.go
Normal file
78
internal/app/init/internal/platform/cloud/azure/azure.go
Normal file
@ -0,0 +1,78 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
|
||||
const (
|
||||
// AzureUserDataEndpoint is the local endpoint for the user data.
|
||||
// By specifying format=text and drilling down to the actual key we care about
|
||||
// we get a base64 encoded userdata response
|
||||
AzureUserDataEndpoint = "http://169.254.169.254/metadata/instance/compute/customData?api-version=2019-06-01&format=text"
|
||||
// AzureHostnameEndpoint is the local endpoint for the hostname.
|
||||
AzureHostnameEndpoint = "http://169.254.169.254/metadata/instance/compute/name?api-version=2019-06-01&format=text"
|
||||
// AzureInternalEndpoint is the Azure Internal Channel IP
|
||||
// https://blogs.msdn.microsoft.com/mast/2015/05/18/what-is-the-ip-address-168-63-129-16/
|
||||
AzureInternalEndpoint = "http://168.63.129.16"
|
||||
)
|
||||
|
||||
// Azure is the concrete type that implements the platform.Platform interface.
|
||||
type Azure struct{}
|
||||
|
||||
// Name implements the platform.Platform interface.
|
||||
func (a *Azure) Name() string {
|
||||
return "Azure"
|
||||
}
|
||||
|
||||
// UserData implements the platform.Platform interface.
|
||||
func (a *Azure) UserData() (*userdata.UserData, error) {
|
||||
if err := linuxAgent(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return userdata.Download(AzureUserDataEndpoint, userdata.WithHeaders(map[string]string{"Metadata": "true"}), userdata.WithFormat("base64"))
|
||||
}
|
||||
|
||||
// Prepare implements the platform.Platform interface and handles initial host preparation.
|
||||
func (a *Azure) Prepare(data *userdata.UserData) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func hostname() (err error) {
|
||||
|
||||
// TODO get this sorted; assuming we need to set appropriate headers
|
||||
return err
|
||||
|
||||
/*
|
||||
resp, err := http.Get(AzureHostnameEndpoint)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// nolint: errcheck
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("download user data: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
dataBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = unix.Sethostname(dataBytes); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
*/
|
||||
}
|
||||
|
||||
// Install implements the platform.Platform interface and handles additional system setup.
|
||||
func (a *Azure) Install(data *userdata.UserData) (err error) {
|
||||
return hostname()
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package azure_test
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
// added for accurate coverage estimation
|
||||
//
|
||||
// please remove it once any unit-test is added
|
||||
// for this package
|
||||
}
|
215
internal/app/init/internal/platform/cloud/azure/register.go
Normal file
215
internal/app/init/internal/platform/cloud/azure/register.go
Normal file
@ -0,0 +1,215 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// This should provide the bare minimum to trigger a node in ready condition to allow
|
||||
// azure to be happy with the node and let it on it's lawn.
|
||||
func linuxAgent() (err error) {
|
||||
var gs *GoalState
|
||||
gs, err = goalState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return reportHealth(gs.Incarnation, gs.Container.ContainerID, gs.Container.RoleInstanceList.RoleInstance.InstanceID)
|
||||
}
|
||||
|
||||
func goalState() (gs *GoalState, err error) {
|
||||
u, err := url.Parse(AzureInternalEndpoint + "/machine/?comp=goalstate")
|
||||
if err != nil {
|
||||
return gs, nil
|
||||
}
|
||||
req, err := http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return gs, err
|
||||
}
|
||||
|
||||
addHeaders(req)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return gs, err
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return gs, err
|
||||
}
|
||||
|
||||
gs = &GoalState{}
|
||||
err = xml.Unmarshal(body, gs)
|
||||
return gs, err
|
||||
}
|
||||
|
||||
func reportHealth(gsIncarnation, gsContainerID, gsInstanceID string) (err error) {
|
||||
// Construct health response
|
||||
h := &Health{
|
||||
Xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
||||
Xsd: "http://www.w3.org/2001/XMLSchema",
|
||||
WAAgent: WAAgent{
|
||||
GoalStateIncarnation: gsIncarnation,
|
||||
Container: &Container{
|
||||
ContainerID: gsContainerID,
|
||||
RoleInstanceList: &RoleInstanceList{
|
||||
Role: &RoleInstance{
|
||||
InstanceID: gsInstanceID,
|
||||
Health: &HealthStatus{
|
||||
State: "Ready",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Encode health response as xml
|
||||
b := new(bytes.Buffer)
|
||||
b.WriteString(xml.Header)
|
||||
err = xml.NewEncoder(b).Encode(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var u *url.URL
|
||||
u, err = url.Parse(AzureInternalEndpoint + "/machine/?comp=health")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var req *http.Request
|
||||
var resp *http.Response
|
||||
req, err = http.NewRequest("POST", u.String(), b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addHeaders(req)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO probably should do some better check here ( verify status code )
|
||||
// nolint: errcheck
|
||||
defer resp.Body.Close()
|
||||
_, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
|
||||
}
|
||||
func addHeaders(req *http.Request) {
|
||||
req.Header.Add("x-ms-agent-name", "WALinuxAgent")
|
||||
req.Header.Add("x-ms-version", "2015-04-05")
|
||||
req.Header.Add("Content-Type", "text/xml;charset=utf-8")
|
||||
}
|
||||
|
||||
// GoalState is the response from the Azure platform when a machine
|
||||
// starts up. Ref:
|
||||
// https://github.com/Azure/WALinuxAgent/blob/b26feb7822f7d4a19507b6762fe1bd280c2ba2de/bin/waagent2.0#L4331
|
||||
// https://github.com/Azure/WALinuxAgent/blob/3be3e1fbf2330303f76961b87d891672e847ce4e/azurelinuxagent/common/protocol/wire.py#L216
|
||||
type GoalState struct {
|
||||
XMLName xml.Name `xml:"GoalState"`
|
||||
Xsi string `xml:"xsi,attr"`
|
||||
Xsd string `xml:"xsd,attr"`
|
||||
WAAgent
|
||||
}
|
||||
|
||||
// Health is the response from the local machine to Azure to denote current
|
||||
// machine state.
|
||||
type Health struct {
|
||||
XMLName xml.Name `xml:"Health"`
|
||||
Xsi string `xml:"xmlns:xsi,attr"`
|
||||
Xsd string `xml:"xmlns:xsd,attr"`
|
||||
WAAgent
|
||||
}
|
||||
|
||||
// WAAgent contains the meat of the data format that is passed between the
|
||||
// Azure platform and the machine.
|
||||
// Mostly, we just care about the Incarnation and Container fields here.
|
||||
type WAAgent struct {
|
||||
Text string `xml:",chardata"`
|
||||
Version string `xml:"Version,omitempty"`
|
||||
Incarnation string `xml:"Incarnation,omitempty"`
|
||||
GoalStateIncarnation string `xml:"GoalStateIncarnation,omitempty"`
|
||||
Machine *Machine `xml:"Machine,omitempty"`
|
||||
Container *Container `xml:"Container,omitempty"`
|
||||
}
|
||||
|
||||
// Container holds the interesting details about a provisioned machine.
|
||||
type Container struct {
|
||||
Text string `xml:",chardata"`
|
||||
ContainerID string `xml:"ContainerId"`
|
||||
RoleInstanceList *RoleInstanceList `xml:"RoleInstanceList"`
|
||||
}
|
||||
|
||||
// RoleInstanceList is a list but only has a single item which is cool I guess.
|
||||
type RoleInstanceList struct {
|
||||
Text string `xml:",chardata"`
|
||||
RoleInstance *RoleInstance `xml:"RoleInstance,omitempty"`
|
||||
Role *RoleInstance `xml:"Role,omitempty"`
|
||||
}
|
||||
|
||||
// RoleInstance contains the specifics for the provisioned VM.
|
||||
type RoleInstance struct {
|
||||
Text string `xml:",chardata"`
|
||||
InstanceID string `xml:"InstanceId"`
|
||||
State string `xml:"State,omitempty"`
|
||||
Configuration *Configuration `xml:"Configuration,omitempty"`
|
||||
Health *HealthStatus `xml:"Health,omitempty"`
|
||||
}
|
||||
|
||||
// Configuration seems important but isnt really used right now. We could
|
||||
// very well not include it because we have no use for it right now, but
|
||||
// since we want completeness, we're going to include it.
|
||||
type Configuration struct {
|
||||
Text string `xml:",chardata"`
|
||||
HostingEnvironmentConfig string `xml:"HostingEnvironmentConfig"`
|
||||
SharedConfig string `xml:"SharedConfig"`
|
||||
ExtensionsConfig string `xml:"ExtensionsConfig"`
|
||||
FullConfig string `xml:"FullConfig"`
|
||||
Certificates string `xml:"Certificates"`
|
||||
ConfigName string `xml:"ConfigName"`
|
||||
}
|
||||
|
||||
// Machine holds no useful information for us.
|
||||
type Machine struct {
|
||||
Text string `xml:",chardata"`
|
||||
ExpectedState string `xml:"ExpectedState"`
|
||||
StopRolesDeadlineHint string `xml:"StopRolesDeadlineHint"`
|
||||
LBProbePorts *struct {
|
||||
Text string `xml:",chardata"`
|
||||
Port string `xml:"Port"`
|
||||
} `xml:"LBProbePorts,omitempty"`
|
||||
ExpectHealthReport string `xml:"ExpectHealthReport"`
|
||||
}
|
||||
|
||||
// HealthStatus provides mechanism to trigger Azure to understand that our
|
||||
// machine has transitioned to a 'Ready' state and is good to go.
|
||||
// We can fill out details if we want to be more verbose...
|
||||
type HealthStatus struct {
|
||||
Text string `xml:",chardata"`
|
||||
State string `xml:"State"`
|
||||
Details *struct {
|
||||
Text string `xml:",chardata"`
|
||||
SubStatus string `xml:"SubStatus"`
|
||||
Description string `xml:"Description"`
|
||||
} `xml:"Details,omitempty"`
|
||||
}
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/app/init/internal/platform/baremetal"
|
||||
"github.com/talos-systems/talos/internal/app/init/internal/platform/cloud/aws"
|
||||
"github.com/talos-systems/talos/internal/app/init/internal/platform/cloud/azure"
|
||||
"github.com/talos-systems/talos/internal/app/init/internal/platform/cloud/googlecloud"
|
||||
"github.com/talos-systems/talos/internal/app/init/internal/platform/cloud/packet"
|
||||
"github.com/talos-systems/talos/internal/app/init/internal/platform/cloud/vmware"
|
||||
@ -35,16 +36,18 @@ func NewPlatform() (p Platform, err error) {
|
||||
switch *platform {
|
||||
case "aws":
|
||||
p = &aws.AWS{}
|
||||
case "googlecloud":
|
||||
p = &googlecloud.GoogleCloud{}
|
||||
case "vmware":
|
||||
p = &vmware.VMware{}
|
||||
case "azure":
|
||||
p = &azure.Azure{}
|
||||
case "bare-metal":
|
||||
p = &baremetal.BareMetal{}
|
||||
case "packet":
|
||||
p = &packet.Packet{}
|
||||
case "googlecloud":
|
||||
p = &googlecloud.GoogleCloud{}
|
||||
case "iso":
|
||||
p = &iso.ISO{}
|
||||
case "packet":
|
||||
p = &packet.Packet{}
|
||||
case "vmware":
|
||||
p = &vmware.VMware{}
|
||||
default:
|
||||
return nil, errors.Errorf("platform not supported: %s", *platform)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user