Skip to content
Merged
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
38 changes: 32 additions & 6 deletions api/pkg/apis/v1alpha1/managers/solution/solution-manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type SolutionManager struct {
KeyLockProvider keylock.IKeyLockProvider
IsTarget bool
TargetNames []string
TargetNamespace string
ApiClientHttp api_utils.ApiClient
}

Expand Down Expand Up @@ -122,6 +123,11 @@ func (s *SolutionManager) Init(context *contexts.VendorContext, config managers.

s.TargetNames = strings.Split(targetNames, ",")

s.TargetNamespace = "default"
if v, ok := config.Properties["targetNamespace"]; ok && strings.TrimSpace(v) != "" {
s.TargetNamespace = v
}

if s.IsTarget {
if len(s.TargetNames) == 0 {
return errors.New("target mode is set but target name is not set")
Expand All @@ -134,7 +140,7 @@ func (s *SolutionManager) Init(context *contexts.VendorContext, config managers.
return err
}
}
s.ApiClientHttp, err = api_utils.GetParentApiClient(s.Context.SiteInfo.ParentSite.BaseUrl)
s.ApiClientHttp, err = api_utils.GetParentApiClient(s.Context.SiteInfo.CurrentSite.BaseUrl)
if err != nil {
return err
}
Expand Down Expand Up @@ -682,15 +688,20 @@ func (s *SolutionManager) Enabled() bool {
return s.Config.Properties["poll.enabled"] == "true"
}
func (s *SolutionManager) Poll() []error {
if s.Config.Properties["poll.enabled"] == "true" && s.Context.SiteInfo.ParentSite.BaseUrl != "" && s.IsTarget {
log.InfofCtx(context.Background(), " M (Solution): Poll() called, pollEnabled=%s, currentUrl=%s, isTarget=%v, targetNames=%v",
s.Config.Properties["poll.enabled"], s.Context.SiteInfo.CurrentSite.BaseUrl, s.IsTarget, s.TargetNames)
if s.Config.Properties["poll.enabled"] == "true" && s.Context.SiteInfo.CurrentSite.BaseUrl != "" && s.IsTarget {
log.InfofCtx(context.Background(), " M (Solution): conditions met, starting to process %d targets", len(s.TargetNames))
for _, target := range s.TargetNames {
namespace := s.TargetNamespace
catalogs, err := s.ApiClientHttp.GetCatalogsWithFilter(context.Background(), "", "label", "staged_target="+target,
s.Context.SiteInfo.ParentSite.Username,
s.Context.SiteInfo.ParentSite.Password)
s.Context.SiteInfo.CurrentSite.Username,
s.Context.SiteInfo.CurrentSite.Password)
if err != nil {
return []error{err}
}
for _, c := range catalogs {
namespace = c.ObjectMeta.Namespace
if vs, ok := c.Spec.Properties["deployment"]; ok {
deployment := model.DeploymentSpec{}
jData, _ := json.Marshal(vs)
Expand Down Expand Up @@ -719,13 +730,28 @@ func (s *SolutionManager) Poll() []error {
err = s.ApiClientHttp.ReportCatalogs(context.Background(),
deployment.Instance.ObjectMeta.Name+"-"+target,
components,
s.Context.SiteInfo.ParentSite.Username,
s.Context.SiteInfo.ParentSite.Password)
s.Context.SiteInfo.CurrentSite.Username,
s.Context.SiteInfo.CurrentSite.Password)
if err != nil {
return []error{err}
}
}
}
// Report target status on every poll cycle regardless of whether catalogs are staged
targetReportErr := s.ApiClientHttp.ReportTargetStatus(context.Background(),
target,
namespace,
map[string]string{
"agent.status": "online",
"agent.lastReport": time.Now().UTC().Format(time.RFC3339),
},
s.Context.SiteInfo.CurrentSite.Username,
s.Context.SiteInfo.CurrentSite.Password)
if targetReportErr != nil {
log.WarnfCtx(context.Background(), " M (Solution): failed to report target status for target %s: %v", target, targetReportErr)
} else {
log.InfofCtx(context.Background(), " M (Solution): successfully reported target status for target %s in namespace %s", target, namespace)
}
}
}
return nil
Expand Down
35 changes: 32 additions & 3 deletions api/pkg/apis/v1alpha1/utils/apiclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type (
tokenProvider TokenProvider
client *http.Client
caCertPath string
useSATokens bool
}

ApiClientOption func(*apiClient)
Expand Down Expand Up @@ -87,6 +88,7 @@ type (
SyncStageStatus(ctx context.Context, status model.StageStatus, user string, password string) error
SendVisualizationPacket(ctx context.Context, payload []byte, user string, password string) error
ReportCatalogs(ctx context.Context, instance string, components []model.ComponentSpec, user string, password string) error
ReportTargetStatus(ctx context.Context, target string, namespace string, properties map[string]string, user string, password string) error
CreateSolutionContainer(ctx context.Context, instanceContainer string, payload []byte, namespace string, user string, password string) error
DeleteSolutionContainer(ctx context.Context, instanceContainer string, namespace string, user string, password string) error
GetSolutionContainer(ctx context.Context, instanceContainer string, namespace string, user string, password string) (model.SolutionContainerState, error)
Expand Down Expand Up @@ -140,6 +142,7 @@ func noTokenProvider(ctx context.Context, baseUrl string, client *http.Client, u

func WithUserPassword(ctx context.Context) ApiClientOption {
return func(a *apiClient) {
a.useSATokens = false
a.tokenProvider = func(ctx context.Context, baseUrl string, _ *http.Client, user string, password string) (string, error) {
request := AuthRequest{UserName: user, Password: password}
requestData, _ := json.Marshal(request)
Expand All @@ -161,6 +164,7 @@ func WithUserPassword(ctx context.Context) ApiClientOption {

func WithServiceAccountToken() ApiClientOption {
return func(a *apiClient) {
a.useSATokens = true
a.tokenProvider = func(ctx context.Context, _ string, _ *http.Client, _ string, _ string) (string, error) {
path := os.Getenv(constants.SATokenPathName)
if path == "" {
Expand Down Expand Up @@ -198,6 +202,7 @@ func NewApiClient(ctx context.Context, baseUrl string, opts ...ApiClientOption)
baseUrl: baseUrl,
tokenProvider: noTokenProvider,
client: client,
useSATokens: false,
}

for _, opt := range opts {
Expand Down Expand Up @@ -738,6 +743,28 @@ func (a *apiClient) ReportCatalogs(ctx context.Context, instance string, compone
return nil
}

func (a *apiClient) ReportTargetStatus(ctx context.Context, target string, namespace string, properties map[string]string, user string, password string) error {
token, err := a.tokenProvider(ctx, a.baseUrl, a.client, user, password)
if err != nil {
return err
}
if properties == nil {
properties = map[string]string{}
}
path := "targets/status/" + url.QueryEscape(target) + "?namespace=" + url.QueryEscape(namespace)
payload := map[string]interface{}{
"status": map[string]interface{}{
"properties": properties,
},
}
jData, _ := json.Marshal(payload)
_, err = a.callRestAPI(ctx, path, "PUT", jData, token)
if err != nil {
return err
}
return nil
}

func (a *apiClient) UpsertSolution(ctx context.Context, solution string, payload []byte, namespace string, user string, password string) error {
token, err := a.tokenProvider(ctx, a.baseUrl, a.client, user, password)
if err != nil {
Expand Down Expand Up @@ -834,7 +861,9 @@ func (a *apiClient) SendVisualizationPacket(ctx context.Context, payload []byte,
}

func (a *apiClient) callRestAPI(ctx context.Context, route string, method string, payload []byte, token string) ([]byte, error) {
urlString := fmt.Sprintf("%s%s", a.baseUrl, path.Clean(route))
baseURL := strings.TrimRight(a.baseUrl, "/")
cleanRoute := strings.TrimLeft(path.Clean(route), "/")
urlString := fmt.Sprintf("%s/%s", baseURL, cleanRoute)
ctx, span := observability.StartSpan("Symphony-API-Client", ctx, &map[string]string{
"method": "callRestAPI",
"http.method": method,
Expand Down Expand Up @@ -896,8 +925,8 @@ func (a *apiClient) callRestAPI(ctx context.Context, route string, method string
if resp.StatusCode >= 300 {
if resp.StatusCode == http.StatusForbidden {
// 403 is a retriable error, so we return a COAError with the same status code
// This should only happen at k8s token provider so can skip the username and password
if ShouldUseSATokens() {
// Refresh service-account token only when this client was configured to use SA tokens.
if a.useSATokens {
token, err := a.tokenProvider(ctx, a.baseUrl, a.client, "", "")
if err != nil {
return err
Expand Down
24 changes: 21 additions & 3 deletions api/pkg/apis/v1alpha1/utils/symphony-api.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"

Expand All @@ -29,7 +30,6 @@ import (

var (
SymphonyAPIAddressBase = "http://symphony-service:8080/v1alpha2/"
useSAToken = os.Getenv(constants.UseServiceAccountTokenEnvName)
apiCertPath = os.Getenv(constants.ApiCertEnvName)
)

Expand Down Expand Up @@ -70,8 +70,10 @@ func getApiClient() (*apiClient, error) {
}

if ShouldUseSATokens() {
log.Infof("Configuring API client with service account token provider")
clientOptions = append(clientOptions, WithServiceAccountToken())
} else {
log.Infof("Configuring API client with user/password token provider")
clientOptions = append(clientOptions, WithUserPassword(context.TODO()))
}

Expand All @@ -89,6 +91,7 @@ func GetParentApiClient(baseUrl string) (*apiClient, error) {
clientOptions = append(clientOptions, WithCertAuth(caCert))
}

log.Infof("Configuring parent API client with user/password token provider for baseUrl: %s", baseUrl)
clientOptions = append(clientOptions, WithUserPassword(context.TODO()))
client, err := NewApiClient(context.Background(), baseUrl, clientOptions...)
if err != nil {
Expand All @@ -98,11 +101,26 @@ func GetParentApiClient(baseUrl string) (*apiClient, error) {
}

func ShouldUseSATokens() bool {
return !ShouldUseUserCreds()
raw, ok := os.LookupEnv(constants.UseServiceAccountTokenEnvName)
if !ok {
return true
}

raw = strings.TrimSpace(raw)
if raw == "" {
return true
}

v, err := strconv.ParseBool(strings.ToLower(raw))
if err != nil {
log.Warnf("Invalid value for %s: %q; defaulting to service account token auth", constants.UseServiceAccountTokenEnvName, raw)
return true
}
return v
}

func ShouldUseUserCreds() bool {
return strings.ToLower(os.Getenv(constants.UseServiceAccountTokenEnvName)) == "false"
return !ShouldUseSATokens()
}

var log = logger.NewLogger("coa.runtime")
Expand Down
1 change: 1 addition & 0 deletions api/symphony-agent.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"shutdownGracePeriod": "30s",
"siteInfo": {
"siteId": "hq",
"currentSite": {
Expand Down
Loading
Loading