Skip to content

Commit

Permalink
HMS-5292: Add list subs-as-features
Browse files Browse the repository at this point in the history
  • Loading branch information
rverdile committed Jan 21, 2025
1 parent f070bf4 commit dda1da5
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 35 deletions.
3 changes: 3 additions & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ packages:
github.com/content-services/content-sources-backend/pkg/candlepin_client:
interfaces:
CandlepinClient:
github.com/content-services/content-sources-backend/pkg/admin_client:
interfaces:
AdminClient:
github.com/content-services/content-sources-backend/pkg/cache:
interfaces:
Cache:
Expand Down
63 changes: 63 additions & 0 deletions pkg/admin_client/admin_client_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

128 changes: 128 additions & 0 deletions pkg/admin_client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package admin_client

import (
"context"
"encoding/json"
"fmt"
"github.com/content-services/content-sources-backend/pkg/config"
"io"
"net/http"
"os"
"time"
)

type AdminClient interface {
ListFeatures(ctx context.Context) (features FeaturesResponse, statusCode int, err error)
}

type adminClientImpl struct {
client http.Client
}

func NewAdminClient() (AdminClient, error) {
httpClient, err := getHTTPClient()
if err != nil {
return nil, err
}
return adminClientImpl{client: httpClient}, nil
}

type FeaturesResponse struct {
Content []Content `json:"content"`
}

type Content struct {
Name string `json:"name"`
Rules Rules `json:"rules"`
}

type Rules struct {
MatchProducts []MatchProducts `json:"matchProducts"`
}

type MatchProducts struct {
EngIDs []int `json:"engIds"`
}

func (ac adminClientImpl) ListFeatures(ctx context.Context) (FeaturesResponse, int, error) {
statusCode := http.StatusInternalServerError
var err error

req, err := http.NewRequestWithContext(ctx, http.MethodGet, config.Get().Clients.SubsAsFeatures.Server, nil)
if err != nil {
return FeaturesResponse{}, 0, err
}

var body []byte
resp, err := ac.client.Do(req)
if resp != nil {
defer resp.Body.Close()

if resp.StatusCode != 0 {
statusCode = resp.StatusCode
}

body, err = io.ReadAll(resp.Body)
if err != nil {
return FeaturesResponse{}, http.StatusInternalServerError, fmt.Errorf("error during read response body: %w", err)
}
}
if err != nil {
return FeaturesResponse{}, statusCode, fmt.Errorf("error during GET request: %w", err)
}
if statusCode < 200 || statusCode >= 300 {
return FeaturesResponse{}, statusCode, fmt.Errorf("unexpected status code with body: %s", string(body))
}

var featResp FeaturesResponse
err = json.Unmarshal(body, &featResp)
if err != nil {
return FeaturesResponse{}, statusCode, fmt.Errorf("error during unmarshal response body: %w", err)
}

return featResp, statusCode, nil
}

func getHTTPClient() (http.Client, error) {
timeout := 90 * time.Second

var cert []byte
if config.Get().Clients.SubsAsFeatures.ClientCert != "" {
cert = []byte(config.Get().Clients.SubsAsFeatures.ClientCert)
} else if config.Get().Clients.SubsAsFeatures.ClientCertPath != "" {
file, err := os.ReadFile(config.Get().Clients.SubsAsFeatures.ClientCertPath)
if err != nil {
return http.Client{}, err
}
cert = file
}

var key []byte
if config.Get().Clients.SubsAsFeatures.ClientKey != "" {
key = []byte(config.Get().Clients.SubsAsFeatures.ClientKey)
} else if config.Get().Clients.SubsAsFeatures.ClientKeyPath != "" {
file, err := os.ReadFile(config.Get().Clients.SubsAsFeatures.ClientKeyPath)
if err != nil {
return http.Client{}, err
}
key = file
}

var caCert []byte
if config.Get().Clients.SubsAsFeatures.CACert != "" {
caCert = []byte(config.Get().Clients.SubsAsFeatures.CACert)
} else if config.Get().Clients.SubsAsFeatures.CACertPath != "" {
file, err := os.ReadFile(config.Get().Clients.SubsAsFeatures.CACertPath)
if err != nil {
return http.Client{}, err
}
caCert = file
}

transport, err := config.GetTransport(cert, key, caCert, timeout)
if err != nil {
return http.Client{}, fmt.Errorf("error creating http transport: %w", err)
}

return http.Client{Transport: transport, Timeout: timeout}, nil
}
4 changes: 4 additions & 0 deletions pkg/api/admin_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ type pulpProgressReportResponse struct {
Suffix zest.NullableString `json:"suffix,omitempty"`
}

type SubsAsFeaturesResponse struct {
Features []string `json:"features"`
}

func (a *AdminTaskInfoCollectionResponse) SetMetadata(meta ResponseMetadata, links Links) {
a.Meta = meta
a.Links = links
Expand Down
29 changes: 4 additions & 25 deletions pkg/candlepin_client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package candlepin_client

import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net/http"
Expand Down Expand Up @@ -37,40 +35,21 @@ func errorWithResponseBody(message string, httpResp *http.Response, err error) e
func getHTTPClient() (http.Client, error) {
timeout := 90 * time.Second
transport := &http.Transport{ResponseHeaderTimeout: timeout}
var err error

certStr := config.Get().Clients.Candlepin.ClientCert
keyStr := config.Get().Clients.Candlepin.ClientKey
ca := config.Get().Clients.Candlepin.CACert

if certStr != "" {
cert, err := tls.X509KeyPair([]byte(certStr), []byte(keyStr))
transport, err = config.GetTransport([]byte(certStr), []byte(keyStr), []byte(ca), timeout)
if err != nil {
return http.Client{}, fmt.Errorf("could not load cert pair for candlepin %w", err)
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
}
if ca != "" {
pool, err := certPool(ca)
if err != nil {
return http.Client{}, err
}
tlsConfig.RootCAs = pool
return http.Client{}, fmt.Errorf("could not create http transport: %w", err)
}
transport.TLSClientConfig = tlsConfig
}
return http.Client{Transport: transport, Timeout: timeout}, nil
}

func certPool(caCert string) (*x509.CertPool, error) {
pool := x509.NewCertPool()
ok := pool.AppendCertsFromPEM([]byte(caCert))
if !ok {
return nil, fmt.Errorf("could not parse candlepin ca cert")
}
return pool, nil
}

func getCorrelationId(ctx context.Context) string {
value := ctx.Value(config.ContextRequestIDKey{})
if value != nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/config/certificates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package config
65 changes: 59 additions & 6 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ type Configuration struct {
}

type Clients struct {
RbacEnabled bool `mapstructure:"rbac_enabled"`
RbacBaseUrl string `mapstructure:"rbac_base_url"`
RbacTimeout int `mapstructure:"rbac_timeout"`
Pulp Pulp `mapstructure:"pulp"`
Redis Redis `mapstructure:"redis"`
Candlepin Candlepin `mapstructure:"candlepin"`
RbacEnabled bool `mapstructure:"rbac_enabled"`
RbacBaseUrl string `mapstructure:"rbac_base_url"`
RbacTimeout int `mapstructure:"rbac_timeout"`
Pulp Pulp `mapstructure:"pulp"`
Redis Redis `mapstructure:"redis"`
Candlepin Candlepin `mapstructure:"candlepin"`
SubsAsFeatures SubsAsFeatures `mapstructure:"subs_as_features"`
}

type Mocks struct {
Expand Down Expand Up @@ -96,6 +97,16 @@ type Candlepin struct {
DevelOrg bool `mapstructure:"devel_org"` // For use only in dev envs
}

type SubsAsFeatures struct {
Server string
ClientCert string `mapstructure:"client_cert"`
ClientKey string `mapstructure:"client_key"`
CACert string `mapstructure:"ca_cert"`
ClientCertPath string `mapstructure:"client_cert_path"`
ClientKeyPath string `mapstructure:"client_key_path"`
CACertPath string `mapstructure:"ca_cert_path"`
}

const RepoClowderBucketName = "content-sources-central-pulp-s3"

type ObjectStore struct {
Expand Down Expand Up @@ -296,6 +307,14 @@ func setDefaults(v *viper.Viper) {
v.SetDefault("clients.redis.expiration.pulp_content_path", 1*time.Hour)
v.SetDefault("clients.redis.expiration.subscription_check", 1*time.Hour)

v.SetDefault("clients.subs_as_features.server", "")
v.SetDefault("clients.subs_as_features.client_cert", "")
v.SetDefault("clients.subs_as_features.client_key", "")
v.SetDefault("clients.subs_as_features.ca_cert", "")
v.SetDefault("clients.subs_as_features.client_cert_path", "")
v.SetDefault("clients.subs_as_features.client_key_path", "")
v.SetDefault("clients.subs_as_features.ca_cert_path", "")

v.SetDefault("tasking.heartbeat", 1*time.Minute)
v.SetDefault("tasking.worker_count", 3)
v.SetDefault("tasking.pgx_logging", true)
Expand Down Expand Up @@ -472,6 +491,40 @@ func ConfigureCertificate() (*tls.Certificate, *string, error) {
return &cert, &certString, nil
}

func GetTransport(certBytes, keyBytes, caCertBytes []byte, timeout time.Duration) (*http.Transport, error) {
transport := &http.Transport{ResponseHeaderTimeout: timeout}

if certBytes != nil && keyBytes != nil {
cert, err := tls.X509KeyPair(certBytes, keyBytes)
if err != nil {
return transport, fmt.Errorf("could not load keypair: %w", err)
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
}

if caCertBytes != nil {
pool, err := certPool(caCertBytes)
if err != nil {
return transport, err
}
tlsConfig.RootCAs = pool
}
transport.TLSClientConfig = tlsConfig
}
return transport, nil
}

func certPool(caCert []byte) (*x509.CertPool, error) {
pool := x509.NewCertPool()
ok := pool.AppendCertsFromPEM(caCert)
if !ok {
return nil, fmt.Errorf("could not parse candlepin ca cert")
}
return pool, nil
}

func CDNCertDaysTillExpiration() (int, error) {
if Get().Certs.CdnCertPair == nil {
return 0, nil
Expand Down
Loading

0 comments on commit dda1da5

Please sign in to comment.