This is a simulation of the metadata server that run on cloud environments of such providers as Amazon, Google or Azure. This server is intended to assist in local debugging of applications that are intended for run in cloud environments and make use of the environment's metadata server.
The default configuration of the metadataserver package sets up the following endpoint:
http://169.254.169.254/computeMetadata/v1
All other endpoints are served by appending the path of the metadata to the default endpoint path.
Note
Currently metadataserver does not support HTTPS endpoints
To use the package do the following:
-
Import the package into your code:
import "github.com/minherz/metadataserver"
-
Instantiate the server:
ms, err := metadataserver.New(metadataserver.WithConfigFile("path/to/config/file"))
See other [options] for more configurations.
-
Start the server:
err := ms.Start(context.Background())
-
Stop the server:
err := ms.Stop(context.Background())
This package can be used for local unit testing of the code that uses metadata server.
Important
If your code sends requests to the metadata server using IP address, make sure that this IP address is reachable. Reference to Metadata server IP address section for more details.
If your code sends requests to the metadata server using hostname, edit hostname file to link the hostname to localhost.
In your test file start and stop the server as shown above.
The following example configures the metadata server to run on interloop interface, listening at port 80 and serving requests at the following two endpoints:
- computeMetadata/v1/project-id
- computeMetadata/v1/instance/zone
package service_test
import (
"context"
"testing"
"github.com/org/project/service"
"github.com/minherz/metadataserver"
)
func TestMyService(t *testing.T) {
if testing.Short() {
t.Skip()
}
tests := []struct {
name string
// rest of input/want/expected data
}{
name: "test1",
}}
s, err := metadataserver.New(
metadataserver.WithAddress("0.0.0.0"),
metadataserver.WithPort(80),
metadataserver.WithHandlers(
map[string]metadataserver.Metadata {
"project-id": func() string { return "your-test-project" },
"instance/zone": func() string { return "us-central1" },
}))
if err != nil {
t.Errorf("expected no errors, got: %v", err)
}
err = s.Start()
if err != nil {
t.Errorf("expected no errors, got: %v", err)
}
defer s.Stop()
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// you test code
})
}
}You can initialize server with the following options:
WithConfigFile()-- allows to configure server using the JSON configuration file. See Custom configuration for the file format. Mind the order of options when use withWithConfiguration(),WithAddress()andWithPort().WithConfiguration()-- allows to configure server with theConfigurationobject. Mind the order of options when use withWithConfigFile(),WithAddress()andWithPort().WithAddress()-- allows to set up the serving address for the server. Mind the order of options when use withWithConfigFile()andWithConfiguration().WithPort()-- allows to set up the port that the server will be listening at. Mind the order of options when use withWithConfigFile()andWithConfiguration().WithEndpoint()-- allows to set up the default endpoint path. Mind the order of options when use withWithConfigFile()andWithConfiguration().WithHandlers()-- allows to set up the metadata paths and responses when the metadata request is served at the paths. Mind the order of options when use withWithConfigFile()andWithConfiguration().WithLogger-- allows to setup a customslog.Logger. If no logger is set up the metadata server writes logs toio.Discard.
You can define custom configurations using JSON configuration file instead of setting them up in the code.
See example configurations in the repo.
You also can use WithConfiguration() option to define the configuration in the code instead of using other With* functions.
The JSON file schema is described below:
| Name | Type | Description |
|---|---|---|
address |
string |
IP address of where the server serves the requests. Default value 169.254.169.254. |
port |
numeric |
Port number at which the server listens. Default value 80. |
endpoint |
string |
The default path. Together with address and port it defined the default endpoint and also is used as a prefix for other handler's paths. Sending request to the default endpoint always returns "ok". Default value computeMetadata/v1. |
shutdownTimeout |
numeric |
The time in seconds that takes to server to timeout at shutdown. Default value 5 (sec). |
metadata |
map | Collection of key-values describing the returned metadata. See next paragraph for more information. |
Metadata maps keys to values allowing customization of data that the server returns on different paths. The path is composed of concatinating the endpoint with the metadata's key string.
For example, for the default endpoint and the key "project/project-id", the server will respond at the path "/computeMetadata/v1/project/project-id" with the value defined in the metadata map.
Metadata map supports two types of values:
-
Static values -- literals that are returned when a request is send using the path of the endpoint + key. Use the following JSON to define the static value:
{ "value": "STATIC_VALUE" } -
Environment-based values -- the returned value is retrieved from the environment variable which name is defined in the metadata's value. When a request is send using the path of the endpoint + key, the server will read and return the value of the environment variable. Use the following JSON to define the environment-based value:
{ "env": "ENV_VARIABLE_NAME" }
The following example of the custom configuration sets up the server to serve three metadata values at the following paths:
/custom/endpoint/staticpath will returnalways the same/custom/endpoint/environment_apath will return the value of the environment variable with the nameX_A/custom/endpoint/another/staticpath will returnthis is another always the same value
{
"endpoint": "custom/endpoint",
"metadata": {
"static": {
"value": "always the same"
},
"environment_a": {
"env": "X_A"
},
"another/static": {
"value": "this is another always the same value"
}
}
}Note
Configuration values that were not customized keep their default values. If no metadata is configured, the server will respond at the path defined by the endpoint only.
The package does not implement any networking configuration on the local host.
If you code uses a hostname (e.g. metadata.google.internal for Google's metadata server), this hostname has to be explicitly configured in your debug environment or your code has to run the metadataserver using IP address instead.
Majority of providers run their metadata servers using 169.254.169.254 IP address.
It is a link-local address.
This means that these addresses are usually have to be configured in the environment prior to use.
Use the following Linux shell command to configure link this address to your localhost interface:
sudo ip addr add 169.254.169.254/16 dev loUse the following PowerShell to do the same on Windows:
New-NetIPAddress -InterfaceAlias "Loopback" -IPAddress "169.254.169.254" -PrefixLength 16Note
It is highly unlikely that you already have a link for "169.254.169.254" in your environment. However, take precautions not to override the already existing configuration.