Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Starting with this release, ignition-validate binaries are signed with the
### Features

- Support Oracle Cloud Infrastructure
- IPv6 support for Scaleway metadata endpoint ([#2052](https://github.com/coreos/ignition/pull/2052))

### Changes

Expand Down
28 changes: 18 additions & 10 deletions internal/providers/scaleway/scaleway.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,36 @@ import (

"github.com/coreos/ignition/v2/config/v3_6_experimental/types"
"github.com/coreos/ignition/v2/internal/platform"
"github.com/coreos/ignition/v2/internal/providers/util"
"github.com/coreos/ignition/v2/internal/resource"

"github.com/coreos/vcontext/report"
)

var (
userdataURL = url.URL{
Scheme: "http",
Host: "169.254.42.42",
Path: "user_data/cloud-init",
userdataURLs = map[string]url.URL{
resource.IPv4: {
Scheme: "http",
Host: "169.254.42.42",
Path: "user_data/cloud-init",
},
resource.IPv6: {
Scheme: "http",
Host: "[fd00:42::42]",
Path: "user_data/cloud-init",
},
}
)

func init() {
platform.Register(platform.Provider{
Name: "scaleway",
Fetch: fetchConfig,
Name: "scaleway",
Fetch: func(f *resource.Fetcher) (types.Config, report.Report, error) {
return resource.FetchConfigDualStack(f, userdataURLs, fetchConfig)
},
})
}

func fetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) {
func fetchConfig(f *resource.Fetcher, userdataURL url.URL) ([]byte, error) {
// For security reason, Scaleway requires to query user data with a source port below 1024.
port := func() int {
return rand.Intn(1022) + 1
Expand All @@ -55,8 +63,8 @@ func fetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) {
LocalPort: port,
})
if err != nil && err != resource.ErrNotFound {
return types.Config{}, report.Report{}, err
return nil, err
}

return util.ParseConfig(f.Logger, data)
return data, nil
}
75 changes: 74 additions & 1 deletion internal/resource/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"io"
"net"
"net/http"
"net/netip"
"net/url"
"os"
"strings"
Expand All @@ -36,9 +37,13 @@ import (
configErrors "github.com/coreos/ignition/v2/config/shared/errors"
"github.com/coreos/ignition/v2/internal/log"
"github.com/coreos/ignition/v2/internal/util"
"github.com/coreos/vcontext/report"
"golang.org/x/oauth2/google"
"google.golang.org/api/option"

"github.com/coreos/ignition/v2/config/v3_6_experimental/types"
providersUtil "github.com/coreos/ignition/v2/internal/providers/util"

"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/aws/aws-sdk-go/aws"
Expand All @@ -52,6 +57,11 @@ import (
"github.com/vincent-petithory/dataurl"
)

const (
IPv4 = "ipv4"
IPv6 = "ipv6"
)

var (
ErrSchemeUnsupported = errors.New("unsupported source scheme")
ErrPathNotAbsolute = errors.New("path is not absolute")
Expand Down Expand Up @@ -330,10 +340,17 @@ func (f *Fetcher) fetchFromHTTP(u url.URL, dest io.Writer, opts FetchOptions) er
p int
)

host := u.Hostname()
addr, _ := netip.ParseAddr(host)
network := "tcp6"
if addr.Is4() {
network = "tcp4"
}

// Assert that the port is not already used.
for {
p = opts.LocalPort()
l, err := net.Listen("tcp4", fmt.Sprintf(":%d", p))
l, err := net.Listen(network, fmt.Sprintf(":%d", p))
if err != nil && errors.Is(err, syscall.EADDRINUSE) {
continue
} else if err == nil {
Expand Down Expand Up @@ -725,3 +742,59 @@ func (f *Fetcher) parseARN(arnURL string) (string, string, string, string, error
key := strings.Join(urlSplit[1:], "/")
return bucket, key, "", regionHint, nil
}

// FetchConfigDualStack is a function that takes care of fetching Ignition configuration on systems where IPv4 only, IPv6 only or both are available.
// From a high level point of view, this function will try to fetch in parallel Ignition configuration from IPv4 and/or IPv6 - if both endpoints are available, it will
// return the first configuration successfully fetched.
func FetchConfigDualStack(f *Fetcher, userdataURLs map[string]url.URL, fetchConfig func(*Fetcher, url.URL) ([]byte, error)) (types.Config, report.Report, error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

var (
err error
nbErrors int
)

// cfg holds the configuration for a given IP
cfg := make(map[url.URL][]byte)

// success hold the IP of the first successful configuration fetching
success := make(chan url.URL, 1)
errors := make(chan error, 2)

fetch := func(ctx context.Context, ip url.URL) {
d, e := fetchConfig(f, ip)
if e != nil {
f.Logger.Err("fetching configuration for %s: %v", ip.String(), e)
err = e
errors <- e
} else {
cfg[ip] = d
success <- ip
}
}

if ipv4, ok := userdataURLs[IPv4]; ok {
go fetch(ctx, ipv4)
}

if ipv6, ok := userdataURLs[IPv6]; ok {
go fetch(ctx, ipv6)
}

// Now wait for one success. (i.e wait for the first configuration to be available)
select {
case ip := <-success:
f.Logger.Debug("got configuration from: %s", ip.String())
return providersUtil.ParseConfig(f.Logger, cfg[ip])
case <-errors:
nbErrors++
if nbErrors == 2 {
f.Logger.Debug("all routines have failed to fetch configuration, returning last known error: %v", err)
return types.Config{}, report.Report{}, err
}
}

// we should never reach this line
return types.Config{}, report.Report{}, err
}
Loading