From 6f1cbd851e6212d2a973679aea73cae73a8c04df Mon Sep 17 00:00:00 2001 From: Raphael Philipe Mendes da Silva Date: Wed, 25 Jan 2023 15:41:00 -0800 Subject: [PATCH] Add the httpsprovider component (#6683) * Feat: add httpsprovider component Add the httpsprovider component that allows the collector to fetch configuration from a web server using the https protocol. Signed-off-by: Raphael Silva * Add httpsprovider to the list of config providers Signed-off-by: Raphael Silva * Update README Signed-off-by: Raphael Silva * Apply suggestions from code review Co-authored-by: Pablo Baeyens * Improve unit tests Improve unit tests to check for error while parsing test URL. Co-authored-by: Pablo Baeyens * Fix comments Co-authored-by: Anthony Mirabella * Fix comments Co-authored-by: Anthony Mirabella * Use SchemeType instead of TransportType Signed-off-by: Raphael Silva * Refactor to improve readability Signed-off-by: Raphael Silva * Use consts from http to set http status code Signed-off-by: Raphael Silva * Refactor code organization Signed-off-by: Raphael Silva * Use struct with zero initialized properties Signed-off-by: Raphael Silva Signed-off-by: Raphael Silva Co-authored-by: Pablo Baeyens Co-authored-by: Anthony Mirabella --- .chloggen/add-https-httpprovider.yaml | 16 + confmap/provider/httpprovider/provider.go | 61 +--- .../provider/httpprovider/provider_test.go | 89 +---- confmap/provider/httpsprovider/README.md | 18 + confmap/provider/httpsprovider/provider.go | 30 ++ .../provider/httpsprovider/provider_test.go | 26 ++ .../configurablehttpprovider/provider.go | 132 ++++++++ .../configurablehttpprovider/provider_test.go | 315 ++++++++++++++++++ .../testdata/otel-config.yaml | 0 otelcol/configprovider.go | 3 +- 10 files changed, 545 insertions(+), 145 deletions(-) create mode 100755 .chloggen/add-https-httpprovider.yaml create mode 100644 confmap/provider/httpsprovider/README.md create mode 100644 confmap/provider/httpsprovider/provider.go create mode 100644 confmap/provider/httpsprovider/provider_test.go create mode 100644 confmap/provider/internal/configurablehttpprovider/provider.go create mode 100644 confmap/provider/internal/configurablehttpprovider/provider_test.go rename confmap/provider/{httpprovider => internal/configurablehttpprovider}/testdata/otel-config.yaml (100%) diff --git a/.chloggen/add-https-httpprovider.yaml b/.chloggen/add-https-httpprovider.yaml new file mode 100755 index 00000000000..00c3b7246ec --- /dev/null +++ b/.chloggen/add-https-httpprovider.yaml @@ -0,0 +1,16 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: new_component + +# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver) +component: httpsprovider + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add the httpsprovider. This component allows the collector to fetch configurations from web servers using the HTTPS protocol. + +# One or more tracking issues or pull requests related to the change +issues: [6683] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: diff --git a/confmap/provider/httpprovider/provider.go b/confmap/provider/httpprovider/provider.go index b6834e66145..063fb69e50f 100644 --- a/confmap/provider/httpprovider/provider.go +++ b/confmap/provider/httpprovider/provider.go @@ -15,66 +15,15 @@ package httpprovider // import "go.opentelemetry.io/collector/confmap/provider/httpprovider" import ( - "context" - "fmt" - "io" - "net/http" - "strings" - "go.opentelemetry.io/collector/confmap" - "go.opentelemetry.io/collector/confmap/provider/internal" -) - -const ( - schemeName = "http" + "go.opentelemetry.io/collector/confmap/provider/internal/configurablehttpprovider" ) -type provider struct { - client http.Client -} - -// New returns a new confmap.Provider that reads the configuration from a file. -// -// This Provider supports "http" scheme, and can be called with a "uri" that follows: +// New returns a new confmap.Provider that reads the configuration from a http server. // -// One example for http-uri be like: http://localhost:3333/getConfig +// This Provider supports "http" scheme. // -// Examples: -// `http://localhost:3333/getConfig` - (unix, windows) +// One example for HTTP URI is: http://localhost:3333/getConfig func New() confmap.Provider { - return &provider{client: http.Client{}} -} - -func (fmp *provider) Retrieve(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) { - if !strings.HasPrefix(uri, schemeName+":") { - return nil, fmt.Errorf("%q uri is not supported by %q provider", uri, schemeName) - } - - // send a HTTP GET request - resp, err := fmp.client.Get(uri) - if err != nil { - return nil, fmt.Errorf("unable to download the file via HTTP GET for uri %q, with err: %w ", uri, err) - } - defer resp.Body.Close() - - // check the HTTP status code - if resp.StatusCode != 200 { - return nil, fmt.Errorf("404: resource didn't exist, fail to read the response body from uri %q", uri) - } - - // read the response body - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("fail to read the response body from uri %q, with err: %w ", uri, err) - } - - return internal.NewRetrievedFromYAML(body) -} - -func (*provider) Scheme() string { - return schemeName -} - -func (*provider) Shutdown(context.Context) error { - return nil + return configurablehttpprovider.New(configurablehttpprovider.HTTPScheme) } diff --git a/confmap/provider/httpprovider/provider_test.go b/confmap/provider/httpprovider/provider_test.go index 86e744fd47e..705e476d39a 100644 --- a/confmap/provider/httpprovider/provider_test.go +++ b/confmap/provider/httpprovider/provider_test.go @@ -16,101 +16,14 @@ package httpprovider import ( "context" - "fmt" - "net/http" - "net/http/httptest" - "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "go.opentelemetry.io/collector/confmap/confmaptest" ) -func TestFunctionalityDownloadFileHTTP(t *testing.T) { - fp := New() - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - f, err := os.ReadFile("./testdata/otel-config.yaml") - if err != nil { - w.WriteHeader(404) - _, innerErr := w.Write([]byte("Cannot find the config file")) - if innerErr != nil { - fmt.Println("Write failed: ", innerErr) - } - return - } - w.WriteHeader(200) - _, err = w.Write(f) - if err != nil { - fmt.Println("Write failed: ", err) - } - })) - defer ts.Close() - _, err := fp.Retrieve(context.Background(), ts.URL, nil) - assert.NoError(t, err) - assert.NoError(t, fp.Shutdown(context.Background())) -} - -func TestUnsupportedScheme(t *testing.T) { - fp := New() - _, err := fp.Retrieve(context.Background(), "https://...", nil) - assert.Error(t, err) - assert.NoError(t, fp.Shutdown(context.Background())) -} - -func TestEmptyURI(t *testing.T) { - fp := New() - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(400) - })) - defer ts.Close() - _, err := fp.Retrieve(context.Background(), ts.URL, nil) - require.Error(t, err) - require.NoError(t, fp.Shutdown(context.Background())) -} - -func TestRetrieveFromShutdownServer(t *testing.T) { - fp := New() - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) - ts.Close() - _, err := fp.Retrieve(context.Background(), ts.URL, nil) - assert.Error(t, err) - require.NoError(t, fp.Shutdown(context.Background())) -} - -func TestNonExistent(t *testing.T) { - fp := New() - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(404) - })) - defer ts.Close() - _, err := fp.Retrieve(context.Background(), ts.URL, nil) - assert.Error(t, err) - require.NoError(t, fp.Shutdown(context.Background())) -} - -func TestInvalidYAML(t *testing.T) { - fp := New() - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) - _, err := w.Write([]byte("wrong : [")) - if err != nil { - fmt.Println("Write failed: ", err) - } - })) - defer ts.Close() - _, err := fp.Retrieve(context.Background(), ts.URL, nil) - assert.Error(t, err) - require.NoError(t, fp.Shutdown(context.Background())) -} - -func TestScheme(t *testing.T) { +func TestSupportedScheme(t *testing.T) { fp := New() assert.Equal(t, "http", fp.Scheme()) require.NoError(t, fp.Shutdown(context.Background())) } - -func TestValidateProviderScheme(t *testing.T) { - assert.NoError(t, confmaptest.ValidateProviderScheme(New())) -} diff --git a/confmap/provider/httpsprovider/README.md b/confmap/provider/httpsprovider/README.md new file mode 100644 index 00000000000..270dd1b23dd --- /dev/null +++ b/confmap/provider/httpsprovider/README.md @@ -0,0 +1,18 @@ +### What is the httpsprovider? + +An implementation of `confmap.Provider` for HTTPS (httpsprovider) allows OTEL Collector to use the HTTPS protocol to +load configuration files stored in web servers. + +Expected URI format: +- https://... + +### Prerequistes + +You need to setup a HTTP server with support to HTTPS. The server must have a certificate that can be validated in the +host running the collector using system root certificates. + +### Configuration + +At this moment, this component only support communicating with servers whose certificate can be verified using the root +CA certificates installed in the system. The process of adding more root CA certificates to the system is operating +system dependent. For Linux, please refer to the `update-ca-trust` command. diff --git a/confmap/provider/httpsprovider/provider.go b/confmap/provider/httpsprovider/provider.go new file mode 100644 index 00000000000..ef5ce463ead --- /dev/null +++ b/confmap/provider/httpsprovider/provider.go @@ -0,0 +1,30 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package httpsprovider // import "go.opentelemetry.io/collector/confmap/provider/httpsprovider" + +import ( + "go.opentelemetry.io/collector/confmap" + "go.opentelemetry.io/collector/confmap/provider/internal/configurablehttpprovider" +) + +// New returns a new confmap.Provider that reads the configuration from a https server. +// +// This Provider supports "https" scheme. One example of an HTTPS URI is: https://localhost:3333/getConfig +// +// To add extra CA certificates you need to install certificates in the system pool. This procedure is operating system +// dependent. E.g.: on Linux please refer to the `update-ca-trust` command. +func New() confmap.Provider { + return configurablehttpprovider.New(configurablehttpprovider.HTTPSScheme) +} diff --git a/confmap/provider/httpsprovider/provider_test.go b/confmap/provider/httpsprovider/provider_test.go new file mode 100644 index 00000000000..614a76d389a --- /dev/null +++ b/confmap/provider/httpsprovider/provider_test.go @@ -0,0 +1,26 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package httpsprovider + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSupportedScheme(t *testing.T) { + fp := New() + assert.Equal(t, "https", fp.Scheme()) +} diff --git a/confmap/provider/internal/configurablehttpprovider/provider.go b/confmap/provider/internal/configurablehttpprovider/provider.go new file mode 100644 index 00000000000..da6abff102b --- /dev/null +++ b/confmap/provider/internal/configurablehttpprovider/provider.go @@ -0,0 +1,132 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configurablehttpprovider // import "go.opentelemetry.io/collector/confmap/provider/internal/configurablehttpprovider" + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + + "go.opentelemetry.io/collector/confmap" + "go.opentelemetry.io/collector/confmap/provider/internal" +) + +type SchemeType string + +const ( + HTTPScheme SchemeType = "http" + HTTPSScheme SchemeType = "https" +) + +type provider struct { + scheme SchemeType + caCertPath string // Used for tests + insecureSkipVerify bool // Used for tests +} + +// New returns a new provider that reads the configuration from http server using the configured transport mechanism +// depending on the selected scheme. +// There are two types of transport supported: PlainText (HTTPScheme) and TLS (HTTPSScheme). +// +// One example for http-uri: http://localhost:3333/getConfig +// One example for https-uri: https://localhost:3333/getConfig +// This is used by the http and https external implementations. +func New(scheme SchemeType) confmap.Provider { + return &provider{scheme: scheme} +} + +// Create the client based on the type of scheme that was selected. +func (fmp *provider) createClient() (*http.Client, error) { + switch fmp.scheme { + case HTTPScheme: + return &http.Client{}, nil + case HTTPSScheme: + pool, err := x509.SystemCertPool() + + if err != nil { + return nil, fmt.Errorf("unable to create a cert pool: %w", err) + } + + if fmp.caCertPath != "" { + cert, err := os.ReadFile(filepath.Clean(fmp.caCertPath)) + + if err != nil { + return nil, fmt.Errorf("unable to read CA from %q URI: %w", fmp.caCertPath, err) + } + + if ok := pool.AppendCertsFromPEM(cert); !ok { + return nil, fmt.Errorf("unable to add CA from uri: %s into the cert pool", fmp.caCertPath) + } + } + + return &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: fmp.insecureSkipVerify, + RootCAs: pool, + }, + }, + }, nil + default: + return nil, fmt.Errorf("invalid scheme type: %s", fmp.scheme) + } +} + +func (fmp *provider) Retrieve(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) { + + if !strings.HasPrefix(uri, string(fmp.scheme)+":") { + return nil, fmt.Errorf("%q uri is not supported by %q provider", uri, string(fmp.scheme)) + } + + client, err := fmp.createClient() + + if err != nil { + return nil, fmt.Errorf("unable to configure http transport layer: %w", err) + } + + // send a HTTP GET request + resp, err := client.Get(uri) + if err != nil { + return nil, fmt.Errorf("unable to download the file via HTTP GET for uri %q: %w ", uri, err) + } + defer resp.Body.Close() + + // check the HTTP status code + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to load resource from uri %q. status code: %d", uri, resp.StatusCode) + } + + // read the response body + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("fail to read the response body from uri %q: %w", uri, err) + } + + return internal.NewRetrievedFromYAML(body) +} + +func (fmp *provider) Scheme() string { + return string(fmp.scheme) +} + +func (*provider) Shutdown(context.Context) error { + return nil +} diff --git a/confmap/provider/internal/configurablehttpprovider/provider_test.go b/confmap/provider/internal/configurablehttpprovider/provider_test.go new file mode 100644 index 00000000000..71e9a214aaf --- /dev/null +++ b/confmap/provider/internal/configurablehttpprovider/provider_test.go @@ -0,0 +1,315 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configurablehttpprovider + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "net/http" + "net/http/httptest" + "net/url" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/confmap/confmaptest" +) + +func newConfigurableHTTPProvider(scheme SchemeType) *provider { + return New(scheme).(*provider) +} + +func answerGet(w http.ResponseWriter, r *http.Request) { + f, err := os.ReadFile("./testdata/otel-config.yaml") + if err != nil { + w.WriteHeader(http.StatusNotFound) + _, innerErr := w.Write([]byte("Cannot find the config file")) + if innerErr != nil { + fmt.Println("Write failed: ", innerErr) + } + return + } + w.WriteHeader(http.StatusOK) + _, err = w.Write(f) + if err != nil { + fmt.Println("Write failed: ", err) + } +} + +// Generate a self signed certificate specific for the tests. Based on +// https://go.dev/src/crypto/tls/generate_cert.go +func generateCertificate(hostname string) (cert string, key string, err error) { + priv, err := rsa.GenerateKey(rand.Reader, 2048) + + if err != nil { + return "", "", fmt.Errorf("Failed to generate private key: %w", err) + } + + keyUsage := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign + notBefore := time.Now() + notAfter := notBefore.Add(time.Hour * 12) + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + + if err != nil { + return "", "", fmt.Errorf("Failed to generate serial number: %w", err) + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"Httpprovider Co"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: keyUsage, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: true, + DNSNames: []string{hostname}, + } + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + + if err != nil { + return "", "", fmt.Errorf("Failed to create certificate: %w", err) + } + + certOut, err := os.CreateTemp("", "cert*.pem") + if err != nil { + return "", "", fmt.Errorf("Failed to open cert.pem for writing: %w", err) + } + + if err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return "", "", fmt.Errorf("Failed to write data to cert.pem: %w", err) + } + + if err = certOut.Close(); err != nil { + return "", "", fmt.Errorf("Error closing cert.pem: %w", err) + } + + keyOut, err := os.CreateTemp("", "key*.pem") + + if err != nil { + return "", "", fmt.Errorf("Failed to open key.pem for writing: %w", err) + } + + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) + + if err != nil { + return "", "", fmt.Errorf("Unable to marshal private key: %w", err) + } + + if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { + return "", "", fmt.Errorf("Failed to write data to key.pem: %w", err) + } + + if err := keyOut.Close(); err != nil { + return "", "", fmt.Errorf("Error closing key.pem: %w", err) + } + + return certOut.Name(), keyOut.Name(), nil +} + +func TestFunctionalityDownloadFileHTTP(t *testing.T) { + fp := newConfigurableHTTPProvider(HTTPScheme) + ts := httptest.NewServer(http.HandlerFunc(answerGet)) + defer ts.Close() + _, err := fp.Retrieve(context.Background(), ts.URL, nil) + assert.NoError(t, err) + assert.NoError(t, fp.Shutdown(context.Background())) +} + +func TestFunctionalityDownloadFileHTTPS(t *testing.T) { + certPath, keyPath, err := generateCertificate("localhost") + assert.NoError(t, err) + + invalidCert, err := os.CreateTemp("", "cert*.crt") + assert.NoError(t, err) + _, err = invalidCert.Write([]byte{0, 1, 2}) + assert.NoError(t, err) + + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + assert.NoError(t, err) + ts := httptest.NewUnstartedServer(http.HandlerFunc(answerGet)) + ts.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} + ts.StartTLS() + + defer os.Remove(certPath) + defer os.Remove(keyPath) + defer os.Remove(invalidCert.Name()) + defer ts.Close() + + tests := []struct { + testName string + certPath string + hostName string + useCertificate bool + skipHostnameValidation bool + shouldError bool + }{ + { + testName: "Test valid certificate and name", + certPath: certPath, + hostName: "localhost", + useCertificate: true, + skipHostnameValidation: false, + shouldError: false, + }, + { + testName: "Test valid certificate with invalid name", + certPath: certPath, + hostName: "127.0.0.1", + useCertificate: true, + skipHostnameValidation: false, + shouldError: true, + }, + { + testName: "Test valid certificate with invalid name, skip validation", + certPath: certPath, + hostName: "127.0.0.1", + useCertificate: true, + skipHostnameValidation: true, + shouldError: false, + }, + { + testName: "Test no certificate should fail", + certPath: certPath, + hostName: "localhost", + useCertificate: false, + skipHostnameValidation: false, + shouldError: true, + }, + { + testName: "Test invalid cert", + certPath: invalidCert.Name(), + hostName: "localhost", + useCertificate: true, + skipHostnameValidation: false, + shouldError: true, + }, + { + testName: "Test no cert", + certPath: "no_certificate", + hostName: "localhost", + useCertificate: true, + skipHostnameValidation: false, + shouldError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + fp := newConfigurableHTTPProvider(HTTPSScheme) + // Parse url of the test server to get the port number. + tsURL, err := url.Parse(ts.URL) + require.NoError(t, err) + if tt.useCertificate { + fp.caCertPath = tt.certPath + } + fp.insecureSkipVerify = tt.skipHostnameValidation + _, err = fp.Retrieve(context.Background(), fmt.Sprintf("https://%s:%s", tt.hostName, tsURL.Port()), nil) + if tt.shouldError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestUnsupportedScheme(t *testing.T) { + fp := New(HTTPScheme) + _, err := fp.Retrieve(context.Background(), "https://...", nil) + assert.Error(t, err) + assert.NoError(t, fp.Shutdown(context.Background())) + + fp = New(HTTPSScheme) + _, err = fp.Retrieve(context.Background(), "http://...", nil) + assert.Error(t, err) + assert.NoError(t, fp.Shutdown(context.Background())) +} + +func TestEmptyURI(t *testing.T) { + fp := New(HTTPScheme) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + })) + defer ts.Close() + _, err := fp.Retrieve(context.Background(), ts.URL, nil) + require.Error(t, err) + require.NoError(t, fp.Shutdown(context.Background())) +} + +func TestRetrieveFromShutdownServer(t *testing.T) { + fp := New(HTTPScheme) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + ts.Close() + _, err := fp.Retrieve(context.Background(), ts.URL, nil) + assert.Error(t, err) + require.NoError(t, fp.Shutdown(context.Background())) +} + +func TestNonExistent(t *testing.T) { + fp := New(HTTPScheme) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + defer ts.Close() + _, err := fp.Retrieve(context.Background(), ts.URL, nil) + assert.Error(t, err) + require.NoError(t, fp.Shutdown(context.Background())) +} + +func TestInvalidYAML(t *testing.T) { + fp := New(HTTPScheme) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("wrong : [")) + if err != nil { + fmt.Println("Write failed: ", err) + } + })) + defer ts.Close() + _, err := fp.Retrieve(context.Background(), ts.URL, nil) + assert.Error(t, err) + require.NoError(t, fp.Shutdown(context.Background())) +} + +func TestScheme(t *testing.T) { + fp := New(HTTPScheme) + assert.Equal(t, "http", fp.Scheme()) + require.NoError(t, fp.Shutdown(context.Background())) +} + +func TestValidateProviderScheme(t *testing.T) { + assert.NoError(t, confmaptest.ValidateProviderScheme(New(HTTPScheme))) +} + +func TestInvalidTransport(t *testing.T) { + fp := New("foo") + + _, err := fp.Retrieve(context.Background(), "foo://..", nil) + assert.Error(t, err) +} diff --git a/confmap/provider/httpprovider/testdata/otel-config.yaml b/confmap/provider/internal/configurablehttpprovider/testdata/otel-config.yaml similarity index 100% rename from confmap/provider/httpprovider/testdata/otel-config.yaml rename to confmap/provider/internal/configurablehttpprovider/testdata/otel-config.yaml diff --git a/otelcol/configprovider.go b/otelcol/configprovider.go index 16a10013d18..fdef66ffe09 100644 --- a/otelcol/configprovider.go +++ b/otelcol/configprovider.go @@ -23,6 +23,7 @@ import ( "go.opentelemetry.io/collector/confmap/provider/envprovider" "go.opentelemetry.io/collector/confmap/provider/fileprovider" "go.opentelemetry.io/collector/confmap/provider/httpprovider" + "go.opentelemetry.io/collector/confmap/provider/httpsprovider" "go.opentelemetry.io/collector/confmap/provider/yamlprovider" ) @@ -120,7 +121,7 @@ func newDefaultConfigProviderSettings(uris []string) ConfigProviderSettings { return ConfigProviderSettings{ ResolverSettings: confmap.ResolverSettings{ URIs: uris, - Providers: makeMapProvidersMap(fileprovider.New(), envprovider.New(), yamlprovider.New(), httpprovider.New()), + Providers: makeMapProvidersMap(fileprovider.New(), envprovider.New(), yamlprovider.New(), httpprovider.New(), httpsprovider.New()), Converters: []confmap.Converter{expandconverter.New()}, }, }