mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-14 09:01:13 +02:00
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
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
@-rm -rf build images vendor
|
@-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/pkg/errors"
|
||||||
"github.com/talos-systems/talos/internal/app/init/internal/platform/baremetal"
|
"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/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/googlecloud"
|
||||||
"github.com/talos-systems/talos/internal/app/init/internal/platform/cloud/packet"
|
"github.com/talos-systems/talos/internal/app/init/internal/platform/cloud/packet"
|
||||||
"github.com/talos-systems/talos/internal/app/init/internal/platform/cloud/vmware"
|
"github.com/talos-systems/talos/internal/app/init/internal/platform/cloud/vmware"
|
||||||
@ -35,16 +36,18 @@ func NewPlatform() (p Platform, err error) {
|
|||||||
switch *platform {
|
switch *platform {
|
||||||
case "aws":
|
case "aws":
|
||||||
p = &aws.AWS{}
|
p = &aws.AWS{}
|
||||||
case "googlecloud":
|
case "azure":
|
||||||
p = &googlecloud.GoogleCloud{}
|
p = &azure.Azure{}
|
||||||
case "vmware":
|
|
||||||
p = &vmware.VMware{}
|
|
||||||
case "bare-metal":
|
case "bare-metal":
|
||||||
p = &baremetal.BareMetal{}
|
p = &baremetal.BareMetal{}
|
||||||
case "packet":
|
case "googlecloud":
|
||||||
p = &packet.Packet{}
|
p = &googlecloud.GoogleCloud{}
|
||||||
case "iso":
|
case "iso":
|
||||||
p = &iso.ISO{}
|
p = &iso.ISO{}
|
||||||
|
case "packet":
|
||||||
|
p = &packet.Packet{}
|
||||||
|
case "vmware":
|
||||||
|
p = &vmware.VMware{}
|
||||||
default:
|
default:
|
||||||
return nil, errors.Errorf("platform not supported: %s", *platform)
|
return nil, errors.Errorf("platform not supported: %s", *platform)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user