diff --git a/CHANGELOG.md b/CHANGELOG.md index 807786cd..f50093ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/ziti/client.go b/ziti/client.go index 52643a56..4a97399b 100644 --- a/ziti/client.go +++ b/ziti/client.go @@ -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() diff --git a/ziti/ziti.go b/ziti/ziti.go index 61dcc9c4..cab7b2f1 100644 --- a/ziti/ziti.go +++ b/ziti/ziti.go @@ -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. @@ -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) { @@ -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 { @@ -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)