Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow refreshing a single service. Fixes #462 #463

Merged
merged 2 commits into from
Dec 12, 2023
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Release 0.20.145

- New `Context` API method: `RefreshService`, which allows refreshing a single service, when that's all that's needed.

# Release 0.20.59

- SDK context's will now use the properly prefixed Edge Client API Path for enrollment configurations. Previous versions
Expand Down
21 changes: 21 additions & 0 deletions ziti/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,27 @@ func (self *CtrlClient) GetServices() ([]*rest_model.ServiceDetail, error) {
return services, nil
}

// GetService will fetch the specific service requested. If the service doesn't exist,
// nil will be returned
func (self *CtrlClient) GetService(name string) (*rest_model.ServiceDetail, error) {
params := service.NewListServicesParams()

filter := fmt.Sprintf(`name="%s"`, name)
params.Filter = &filter

resp, err := self.API.Service.ListServices(params, nil)

if err != nil {
return nil, rest_util.WrapErr(err)
}

if len(resp.Payload.Data) > 0 {
return resp.Payload.Data[0], nil
}

return nil, nil
}

// GetServiceTerminators returns the client terminator details for a specific service.
func (self *CtrlClient) GetServiceTerminators(svc *rest_model.ServiceDetail, offset int, limit int) ([]*rest_model.TerminatorClientDetail, int, error) {
params := service.NewListServiceTerminatorsParams()
Expand Down
138 changes: 105 additions & 33 deletions ziti/ziti.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ type Context interface {
// to.
RefreshServices() error

// RefreshService forces the context to refresh just the service with the given name. If the given service isn't
// found, a nil will be returned
RefreshService(serviceName string) (*rest_model.ServiceDetail, error)

// GetServiceTerminators will return a slice of rest_model.TerminatorClientDetail for a specific service name.
// The offset and limit options can be used to page through excessive lists of items. A max of 500 is imposed on
// limit.
Expand Down Expand Up @@ -480,53 +484,88 @@ func (context *ContextImpl) processServiceUpdates(services []*rest_model.Service

// Adds and Updates
for _, s := range services {
isChange := false
valuesDiffer := false
context.processServiceAddOrUpdated(s)
}

_ = context.services.Upsert(*s.Name, s, func(exist bool, valueInMap *rest_model.ServiceDetail, newValue *rest_model.ServiceDetail) *rest_model.ServiceDetail {
isChange = exist
if isChange {
valuesDiffer = !reflect.DeepEqual(newValue, valueInMap)
}
context.refreshServiceQueryMap()
}

return newValue
func (context *ContextImpl) processSingleServiceUpdate(name string, s *rest_model.ServiceDetail) {
// process Deletes
if s == nil {
var deletes []string
context.services.IterCb(func(key string, svc *rest_model.ServiceDetail) {
if *svc.Name == name {
deletes = append(deletes, key)
if context.options.OnServiceUpdate != nil {
context.options.OnServiceUpdate(ServiceRemoved, svc)
}
context.Emit(EventServiceRemoved, svc)
context.deleteServiceSessions(*svc.ID)
}
})

for _, deletedKey := range deletes {
context.services.Remove(deletedKey)
context.intercepts.Remove(deletedKey)
}
} else {
// Adds and Updates
context.processServiceAddOrUpdated(s)
}

context.refreshServiceQueryMap()
}

func (context *ContextImpl) processServiceAddOrUpdated(s *rest_model.ServiceDetail) {
isChange := false
valuesDiffer := false

_ = context.services.Upsert(*s.Name, s, func(exist bool, valueInMap *rest_model.ServiceDetail, newValue *rest_model.ServiceDetail) *rest_model.ServiceDetail {
isChange = exist
if isChange {
context.Emit(EventServiceChanged, s)
} else {
context.Emit(EventServiceAdded, s)
valuesDiffer = !reflect.DeepEqual(newValue, valueInMap)
}

if context.options.OnServiceUpdate != nil {
if isChange {
if valuesDiffer {
context.options.OnServiceUpdate(ServiceChanged, s)
}
} else {
context.services.Set(*s.Name, s)
context.options.OnServiceUpdate(ServiceAdded, s)
return newValue
})

if isChange {
context.Emit(EventServiceChanged, s)
} else {
context.Emit(EventServiceAdded, s)
}

if context.options.OnServiceUpdate != nil {
if isChange {
if valuesDiffer {
context.options.OnServiceUpdate(ServiceChanged, s)
}
} else {
context.services.Set(*s.Name, s)
context.options.OnServiceUpdate(ServiceAdded, s)
}
}

intercept := &edge.InterceptV1Config{}
ok, err := edge.ParseServiceConfig(s, InterceptV1, intercept)
if err != nil {
pfxlog.Logger().Warnf("failed to parse config[%s] for service[%s]", InterceptV1, *s.Name)
} else if ok {
intercept := &edge.InterceptV1Config{}
ok, err := edge.ParseServiceConfig(s, InterceptV1, intercept)
if err != nil {
pfxlog.Logger().Warnf("failed to parse config[%s] for service[%s]", InterceptV1, *s.Name)
} else if ok {
intercept.Service = s
context.intercepts.Set(*s.Name, intercept)
} else {
cltCfg := &edge.ClientConfig{}
ok, err := edge.ParseServiceConfig(s, ClientConfigV1, cltCfg)
if err == nil && ok {
intercept = cltCfg.ToInterceptV1Config()
intercept.Service = s
context.intercepts.Set(*s.Name, intercept)
} else {
cltCfg := &edge.ClientConfig{}
ok, err := edge.ParseServiceConfig(s, ClientConfigV1, cltCfg)
if err == nil && ok {
intercept = cltCfg.ToInterceptV1Config()
intercept.Service = s
context.intercepts.Set(*s.Name, intercept)
}
}
}
}

func (context *ContextImpl) refreshServiceQueryMap() {
serviceQueryMap := map[string]map[string]rest_model.PostureQuery{} //serviceId -> queryId -> query

context.services.IterCb(func(key string, svc *rest_model.ServiceDetail) {
Expand Down Expand Up @@ -618,7 +657,7 @@ func (context *ContextImpl) refreshServices(forceCheck bool) error {
if errors.As(err, &target) {
log.Info("attempting to re-authenticate")
if authErr := context.Authenticate(); authErr != nil {
log.WithError(authErr).Error("unable to re-authenticate during session refresh")
log.WithError(authErr).Error("unable to re-authenticate during services refresh")
return err
}
if services, err = context.CtrlClt.GetServices(); err != nil {
Expand All @@ -636,6 +675,39 @@ func (context *ContextImpl) refreshServices(forceCheck bool) error {
return nil
}

func (context *ContextImpl) RefreshService(serviceName string) (*rest_model.ServiceDetail, error) {
if err := context.ensureApiSession(); err != nil {
return nil, fmt.Errorf("failed to refresh service: %v", err)
}

var err error

log := pfxlog.Logger().WithField("serviceName", serviceName)

log.Debug("refreshing service")

serviceDetail, err := context.CtrlClt.GetService(serviceName)
if err != nil {
target := &service.ListServicesUnauthorized{}
if errors.As(err, &target) {
log.Info("attempting to re-authenticate")
if authErr := context.Authenticate(); authErr != nil {
log.WithError(authErr).Error("unable to re-authenticate during service refresh")
return nil, err
}
if serviceDetail, err = context.CtrlClt.GetService(serviceName); err != nil {
return nil, err
}
} else {
return nil, err
}
}

context.processSingleServiceUpdate(serviceName, serviceDetail)

return serviceDetail, nil
}

func (context *ContextImpl) runSessionRefresh() {
log := pfxlog.Logger()
svcUpdateTick := time.NewTicker(context.options.RefreshInterval)
Expand Down
Loading