diff --git a/utils/discovery/discovery.go b/utils/discovery/discovery.go new file mode 100644 index 0000000..bdcf506 --- /dev/null +++ b/utils/discovery/discovery.go @@ -0,0 +1,55 @@ +package discovery + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" +) + +type DiscoveryEndpoint struct { + LagoonVersion string `json:"lagoon_version"` + AuthorizationEndpoint string `json:"authorization_endpoint"` + SSHTokenExchange SSHTokenExchange `json:"ssh_token_exchange"` + WebhookEndpoint string `json:"webhook_endpoint"` + UIHostname string `json:"ui_url"` +} + +type SSHTokenExchange struct { + TokenHost string `json:"token_endpoint_host"` + TokenPort int `json:"token_endpoint_port"` +} + +// the wellknown path of the lagoon discovery endpoint +const wellKnown = ".well-known/appspecific/sh.lagoon.discovery.json" + +func Discover(hostname string) (*DiscoveryEndpoint, error) { + // check if the hostname provided is actually valid-ish + u, err := url.Parse(strings.TrimRight(hostname, "/")) + if err != nil { + return nil, err + } + switch u.Scheme { + case "": + u.Scheme = "https" + case "http", "https": + default: + return nil, fmt.Errorf("url scheme must be http or https not %s", u.Scheme) + + } + // check the discovery endpoint + resp, err := http.Get(fmt.Sprintf("%s/%s", u, wellKnown)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + // decode the response into the discovery struct + d := &DiscoveryEndpoint{} + decoder := json.NewDecoder(resp.Body) + err = decoder.Decode(d) + if err != nil { + return nil, err + } + return d, nil +} diff --git a/utils/discovery/discovery_test.go b/utils/discovery/discovery_test.go new file mode 100644 index 0000000..1c34cd9 --- /dev/null +++ b/utils/discovery/discovery_test.go @@ -0,0 +1,61 @@ +package discovery + +import ( + "fmt" + "net/http" + "net/http/httptest" + "reflect" + "testing" +) + +func TestDiscover(t *testing.T) { + type args struct { + testserverpayload string + } + tests := []struct { + name string + args args + want *DiscoveryEndpoint + wantErr bool + }{ + { + name: "test1", + args: args{ + testserverpayload: `{"lagoon_version":"v2.17.0","authorization_endpoint":"https://keycloak.lagoon.sh","ssh_token_exchange":{"token_endpoint_host":"token.lagoon.sh","token_endpoint_port":22},"webhook_endpoint":"https://webhookhandler.lagoon.sh","ui_url":"https://ui.lagoon.sh"}`, + }, + want: &DiscoveryEndpoint{ + LagoonVersion: "v2.17.0", + AuthorizationEndpoint: "https://keycloak.lagoon.sh", + WebhookEndpoint: "https://webhookhandler.lagoon.sh", + UIHostname: "https://ui.lagoon.sh", + SSHTokenExchange: SSHTokenExchange{ + TokenPort: 22, + TokenHost: "token.lagoon.sh", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // create a test server with the wellknown path + mux := http.NewServeMux() + mux.HandleFunc(fmt.Sprintf("/%s", wellKnown), func(res http.ResponseWriter, req *http.Request) { + // return the payload + res.Write([]byte(tt.args.testserverpayload)) + }) + // start the test server + svr := httptest.NewServer(mux) + defer svr.Close() + // perform the request + got, err := Discover(svr.URL) + if (err != nil) != tt.wantErr { + t.Errorf("Discover() error = %v, wantErr %v", err, tt.wantErr) + return + } + // check the result is as expected + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Discover() = %v, want %v", got, tt.want) + } + }) + } +}