diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/plugins/PluginSettings.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/plugins/PluginSettings.scala index 351ecb3a241..aabe3f8f748 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/plugins/PluginSettings.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/plugins/PluginSettings.scala @@ -153,7 +153,7 @@ object JsonGlobalPluginLimits { res.copy(licensees = sortedDistinctLicensees) } - private def comp[A](a: Option[A], b: Option[A], compare: (A, A) => A): Option[A] = (a, b) match { + def comp[A](a: Option[A], b: Option[A], compare: (A, A) => A): Option[A] = (a, b) match { case (None, None) => None case (Some(x), None) => Some(x) case (None, Some(y)) => Some(y) @@ -413,7 +413,7 @@ object PluginManagementError { } case class PluginId(value: String) extends AnyVal -object PluginId { +object PluginId { implicit val decoder: JsonDecoder[PluginId] = JsonDecoder[String].mapOrFail(parse) implicit val encoder: JsonEncoder[PluginId] = JsonEncoder[String].contramap(_.value) implicit val transformer: Transformer[PluginId, String] = Transformer.derive[PluginId, String] @@ -431,8 +431,72 @@ object PluginId { } } +/** + * Global license information about many plugins, aggregated such that : + * - more than 1 distinct licensees can exist for the collection of all plugins + * - end date of plugins licenses are aggregated, so that we know how many plugin license expire at a given date + **/ +final case class JsonPluginsLicense( + licensees: Option[NonEmptyChunk[String]], + startDate: Option[ZonedDateTime], + endDates: Option[Map[ZonedDateTime, Int]], + maxNodes: Option[Int] +) { + import JsonGlobalPluginLimits.* + import JsonPluginsLicense.* + def combine(that: JsonPluginsLicense): JsonPluginsLicense = { + // for efficiency : check equality and hash first before field comparison, + // as it will mostly be the case because license information should be similar + if (this == that) this + else { + JsonPluginsLicense( + comp[NonEmptyChunk[String]](this.licensees, that.licensees, _ ++ _), + comp[ZonedDateTime](this.startDate, that.startDate, (x, y) => if (x.isAfter(y)) x else y), + comp[Map[ZonedDateTime, Int]](this.endDates, that.endDates, (x, y) => x.addValues(y)), + comp[Int](this.maxNodes, that.maxNodes, (x, y) => if (x < y) x else y) + ) + } + } +} +object JsonPluginsLicense { + import DateFormaterService.JodaTimeToJava + implicit val dateFieldEncoder: JsonFieldEncoder[ZonedDateTime] = + JsonFieldEncoder[String].contramap(DateFormaterService.serializeZDT) + implicit val encoder: JsonEncoder[JsonPluginsLicense] = DeriveJsonEncoder.gen[JsonPluginsLicense] + + def empty: JsonPluginsLicense = JsonPluginsLicense(None, None, None, None) + + def fromLicenseInfo(info: PluginLicenseInfo): JsonPluginsLicense = { + JsonPluginsLicense( + Some(NonEmptyChunk(info.licensee)), + Some(info.startDate.toJava), + Some(Map((info.endDate.toJava, 1))), + info.maxNodes + ) + } + + private def from(licenses: NonEmptyChunk[PluginLicenseInfo]): JsonPluginsLicense = { + val res = licenses.reduceMapLeft(fromLicenseInfo) { case (lim, lic) => lim.combine(fromLicenseInfo(lic)) } + val sortedDistinctLicensees = res.licensees.map(_.sorted.distinct).flatMap(NonEmptyChunk.fromChunk) + res.copy(licensees = sortedDistinctLicensees) + } + + def from(licenses: Chunk[PluginLicenseInfo]): Option[JsonPluginsLicense] = { + NonEmptyChunk + .fromChunk(licenses) + .map(from(_)) + .flatMap(r => Option.when(r != empty)(r)) + } + + implicit class EndDatesOps(x: Map[ZonedDateTime, Int]) { + def addValues(y: Map[ZonedDateTime, Int]): Map[ZonedDateTime, Int] = { + (x.toList ::: y.toList).groupBy { case (date, _) => date }.map { case (k, v) => (k, v.map { case (_, n) => n }.sum) }.toMap + } + } +} + final case class JsonPluginsSystemDetails( - license: Option[JsonGlobalPluginLimits], + license: Option[JsonPluginsLicense], plugins: Chunk[JsonPluginSystemDetails] ) object JsonPluginsSystemDetails { @@ -441,7 +505,7 @@ object JsonPluginsSystemDetails { implicit val encoder: JsonEncoder[JsonPluginsSystemDetails] = DeriveJsonEncoder.gen[JsonPluginsSystemDetails] def buildDetails(plugins: Chunk[JsonPluginSystemDetails]): JsonPluginsSystemDetails = { - val limits = JsonGlobalPluginLimits.getGlobalLimits(plugins.flatMap(_.license)) + val limits = JsonPluginsLicense.from(plugins.flatMap(_.license)) JsonPluginsSystemDetails(limits, plugins) } } @@ -577,7 +641,7 @@ object RudderPackagePlugin { trait RudderPackageService { import RudderPackageService.* - def updateBase(): IOResult[Option[CredentialError]] + def update(): IOResult[Option[CredentialError]] def listAllPlugins(): IOResult[Chunk[RudderPackagePlugin]] @@ -617,7 +681,7 @@ class RudderPackageCmdService(configCmdLine: String) extends RudderPackageServic case h :: tail => Right((h, tail)) } - override def updateBase(): IOResult[Option[CredentialError]] = { + override def update(): IOResult[Option[CredentialError]] = { // In case of error we need to check the result for { res <- runCmd("update" :: Nil) @@ -693,8 +757,8 @@ class RudderPackageCmdService(configCmdLine: String) extends RudderPackageServic */ trait PluginSystemService { - def list(): IOResult[Either[CredentialError, Chunk[JsonPluginSystemDetails]]] - + def updateIndex(): IOResult[Option[CredentialError]] + def list(): IOResult[Chunk[JsonPluginSystemDetails]] def install(plugins: Chunk[PluginId]): IOResult[Unit] def remove(plugins: Chunk[PluginId]): IOResult[Unit] def updateStatus(status: PluginSystemStatus, plugins: Chunk[PluginId]): IOResult[Unit] @@ -705,8 +769,12 @@ trait PluginSystemService { * Implementation for tests, will do any operation without any error */ class InMemoryPluginSystemService(ref: Ref[Map[PluginId, JsonPluginSystemDetails]]) extends PluginSystemService { - override def list(): UIO[Either[CredentialError, Chunk[JsonPluginSystemDetails]]] = { - ref.get.map(m => Right(Chunk.fromIterable(m.values))) + override def updateIndex(): IOResult[Option[CredentialError]] = { + ZIO.none + } + + override def list(): UIO[Chunk[JsonPluginSystemDetails]] = { + ref.get.map(m => Chunk.fromIterable(m.values)) } override def install(plugins: Chunk[PluginId]): UIO[Unit] = { diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/EndpointsDefinition.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/EndpointsDefinition.scala index 88c366deca2..4d80f8c8dc8 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/EndpointsDefinition.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/EndpointsDefinition.scala @@ -649,6 +649,12 @@ sealed trait PluginInternalApi extends EnumEntry with EndpointSchema with Intern } object PluginInternalApi extends Enum[PluginInternalApi] with ApiModuleProvider[PluginInternalApi] { + case object UpdatePluginsIndex extends PluginInternalApi with ZeroParam with StartsAtVersion21 with SortIndex { + val z: Int = implicitly[Line].value + val description = "Update plugins index and licenses" + val (action, path) = POST / "pluginsinternal" / "update" + override def dataContainer: Option[String] = None + } case object ListPlugins extends PluginInternalApi with ZeroParam with StartsAtVersion21 with SortIndex { val z: Int = implicitly[Line].value val description = "List all plugins" diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RudderJsonResponse.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RudderJsonResponse.scala index 0325f18598f..07212b6ff6a 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RudderJsonResponse.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RudderJsonResponse.scala @@ -256,7 +256,7 @@ object RudderJsonResponse { } } - implicit class ToLiftResponseOne[A](result: IOResult[A]) { + implicit class ToLiftResponseOne[A](result: IOResult[A]) { // ADT that matches error or success to determine the id value to use/compute sealed trait IdTrace { // if no computed id is given, we use the constant one @@ -353,6 +353,43 @@ object RudderJsonResponse { ): LiftResponse = { toLiftResponseOneEither(params, ResponseSchema.fromSchema(schema), SuccessIdTrace(id))(JsonEncoder[B], ev) } + + def toLiftResponseZeroEither( + params: DefaultParams, + schema: ResponseSchema, + id: IdTrace + )(implicit ev: A <:< Either[ResponseError, Any]): LiftResponse = { + implicit val prettify = params.prettify + result + .fold( + err => { + ApiLogger.ResponseError.info(err.fullMsg) + internalError(None, schema, err.fullMsg) + }, + either => { + ev.apply(either) match { + case Left(e) => + e match { + case UnauthorizedError(errorMsg) => unauthorizedError(id.error, schema, errorMsg) + } + case Right(_) => + successZero(schema) + } + } + ) + .runNow + } + def toLiftResponseZeroEither(params: DefaultParams, schema: EndpointSchema, id: Option[String])(implicit + ev: A <:< Either[ResponseError, Any] + ): LiftResponse = { + toLiftResponseZeroEither(params, ResponseSchema.fromSchema(schema), ConstIdTrace(id))(ev) + } + def toLiftResponseZeroEither(params: DefaultParams, schema: EndpointSchema, id: A => Option[String])(implicit + ev: A <:< Either[ResponseError, Any] + ): LiftResponse = { + toLiftResponseZeroEither(params, ResponseSchema.fromSchema(schema), SuccessIdTrace(id))(ev) + } + } // when you don't have any response, just a success implicit class ToLiftResponseZero(result: IOResult[Unit]) { diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/internal/PluginInternalApi.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/internal/PluginInternalApi.scala index dad0e38b324..d2974b16f81 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/internal/PluginInternalApi.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/internal/PluginInternalApi.scala @@ -69,6 +69,7 @@ class PluginInternalApi( def getLiftEndpoints(): List[LiftApiModule] = { API.endpoints.map { + case API.UpdatePluginsIndex => UpdatePluginsIndex case API.ListPlugins => ListPlugins case API.InstallPlugins => InstallPlugins case API.RemovePlugins => RemovePlugins @@ -79,6 +80,19 @@ class PluginInternalApi( implicit val encoder: JsonEncoder[PluginSettings] = DeriveJsonEncoder.gen[PluginSettings] implicit val decoder: JsonDecoder[PluginSettings] = DeriveJsonDecoder.gen[PluginSettings] + object UpdatePluginsIndex extends LiftApiModule0 { + val schema: API.UpdatePluginsIndex.type = API.UpdatePluginsIndex + def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = { + pluginService + .updateIndex() + .chainError("Could not update plugins index") + .tapError(err => ApplicationLoggerPure.Plugin.error(err.fullMsg)) + .map(_.map(err => RudderJsonResponse.UnauthorizedError(Some(err.msg))).toLeft(())) + .toLiftResponseZeroEither(params, schema, None) + } + + } + object ListPlugins extends LiftApiModule0 { val schema: API.ListPlugins.type = API.ListPlugins def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = { @@ -86,8 +100,8 @@ class PluginInternalApi( .list() .chainError("Could not get plugins list") .tapError(err => ApplicationLoggerPure.Plugin.error(err.fullMsg)) - .map(_.left.map(c => RudderJsonResponse.UnauthorizedError(Some(c.fullMsg))).map(JsonPluginsSystemDetails.buildDetails)) - .toLiftResponseOneEither[JsonPluginsSystemDetails](params, schema, None) + .map(JsonPluginsSystemDetails.buildDetails) + .toLiftResponseOne(params, schema, None) } } diff --git a/webapp/sources/rudder/rudder-rest/src/test/resources/api/api_plugins.yml b/webapp/sources/rudder/rudder-rest/src/test/resources/api/api_plugins.yml index 16456e5b57d..2c6e917c1bc 100644 --- a/webapp/sources/rudder/rudder-rest/src/test/resources/api/api_plugins.yml +++ b/webapp/sources/rudder/rudder-rest/src/test/resources/api/api_plugins.yml @@ -13,7 +13,9 @@ response: "test-licensee" ], "startDate" : "2025-01-10T21:53:20+01:00", - "endDate" : "2025-01-10T21:53:20+01:00", + "endDates" : { + "2025-01-10T21:53:20+01:00" : 1 + }, "maxNodes" : 1000000 }, "plugins" : [ @@ -120,7 +122,9 @@ response: "test-licensee" ], "startDate" : "2025-01-10T21:53:20+01:00", - "endDate" : "2025-01-10T21:53:20+01:00", + "endDates" : { + "2025-01-10T21:53:20+01:00" : 1 + }, "maxNodes" : 1000000 }, "plugins" : [ @@ -211,7 +215,9 @@ response: "test-licensee" ], "startDate" : "2025-01-10T21:53:20+01:00", - "endDate" : "2025-01-10T21:53:20+01:00", + "endDates" : { + "2025-01-10T21:53:20+01:00" : 1 + }, "maxNodes" : 1000000 }, "plugins" : [ @@ -302,7 +308,9 @@ response: "test-licensee" ], "startDate" : "2025-01-10T21:53:20+01:00", - "endDate" : "2025-01-10T21:53:20+01:00", + "endDates" : { + "2025-01-10T21:53:20+01:00" : 1 + }, "maxNodes" : 1000000 }, "plugins" : [ @@ -393,7 +401,9 @@ response: "test-licensee" ], "startDate" : "2025-01-10T21:53:20+01:00", - "endDate" : "2025-01-10T21:53:20+01:00", + "endDates" : { + "2025-01-10T21:53:20+01:00" : 1 + }, "maxNodes" : 1000000 }, "plugins" : [ diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins.elm b/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins.elm index a0f082d86c6..aab0db94621 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins.elm @@ -58,7 +58,7 @@ update msg model = ApiPostPlugins res -> case res of Ok t -> - ( model, successNotification ("Plugin " ++ requestTypeText t ++ " successfull, the server should restart") ) + ( model, successNotification ("Plugin " ++ requestTypeText t ++ " successful") ) Err err -> processApiErrorBytes "Error while fetching information" err model diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/ApiCalls.elm b/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/ApiCalls.elm index 39d577974d8..ae93b74bc31 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/ApiCalls.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/ApiCalls.elm @@ -1,6 +1,6 @@ module Plugins.ApiCalls exposing (..) -import Http exposing (emptyBody, expectJson, header, request) +import Http exposing (emptyBody, header, request) import Http.Detailed as Detailed import Plugins.DataTypes exposing (..) import Plugins.JsonDecoder exposing (decodeGetPluginsInfo) @@ -12,6 +12,19 @@ getUrl m url = m.contextPath ++ "/secure/api" ++ url +updateIndex : Model -> Cmd Msg +updateIndex model = + request + { method = "POST" + , headers = [ header "X-Requested-With" "XMLHttpRequest" ] + , url = getUrl model "/pluginsinternal/update" + , body = emptyBody + , expect = Detailed.expectWhatever <| ApiPostPlugins << Result.map (\_ -> UpdateIndex) + , timeout = Nothing + , tracker = Nothing + } + + getPluginInfos : Model -> Cmd Msg getPluginInfos model = request diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/DataTypes.elm b/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/DataTypes.elm index 14bd3692de6..d90e4586554 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/DataTypes.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/DataTypes.elm @@ -9,7 +9,7 @@ import Time.ZonedDateTime exposing (ZonedDateTime) type alias PluginsInfo = - { license : LicenseGlobalInfo + { license : Maybe LicenseGlobalInfo , plugins : List PluginInfo } @@ -17,7 +17,7 @@ type alias PluginsInfo = type alias LicenseGlobalInfo = { licensees : Maybe (List String) , startDate : Maybe ZonedDateTime - , endDate : Maybe ZonedDateTime + , endDates : Maybe (List ( ZonedDateTime, Int )) , maxNodes : Maybe Int } @@ -74,7 +74,7 @@ type alias UI = type alias Model = { contextPath : String - , license : LicenseGlobalInfo + , license : Maybe LicenseGlobalInfo , plugins : List PluginInfo , ui : UI } @@ -92,6 +92,7 @@ type RequestType | Uninstall | Enable | Disable + | UpdateIndex type Msg @@ -122,6 +123,9 @@ requestTypeText t = Disable -> "disable" + UpdateIndex -> + "index update" + processSelect : Select -> Model -> Model processSelect select model = @@ -168,6 +172,6 @@ noGlobalLicense : LicenseGlobalInfo noGlobalLicense = { licensees = Nothing , startDate = Nothing - , endDate = Nothing + , endDates = Nothing , maxNodes = Nothing } diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/Init.elm b/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/Init.elm index d7740bbdb05..9d3962319e6 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/Init.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/Init.elm @@ -16,7 +16,7 @@ init flags = UI [] Nothing initModel = - Model flags.contextPath noGlobalLicense [] initUI + Model flags.contextPath Nothing [] initUI in ( initModel , getPluginInfos initModel diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/JsonDecoder.elm b/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/JsonDecoder.elm index 0c35d419a85..e725439e6cc 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/JsonDecoder.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/JsonDecoder.elm @@ -17,7 +17,7 @@ decodeGetPluginsInfo = decodePluginsInfo : Decoder PluginsInfo decodePluginsInfo = D.succeed PluginsInfo - |> required "license" decodeLicenseGlobalInfo + |> optional "license" (maybe decodeLicenseGlobalInfo) Nothing |> required "plugins" (list decodePluginInfo) @@ -26,7 +26,7 @@ decodeLicenseGlobalInfo = D.succeed LicenseGlobalInfo |> optional "licensees" (maybe (list string)) Nothing |> optional "startDate" (maybe decodeDateTime) Nothing - |> optional "endDate" (maybe decodeDateTime) Nothing + |> optional "endDates" (maybe decodeDateInts) Nothing |> optional "maxNodes" (maybe int) Nothing @@ -98,16 +98,31 @@ decodeLicenseInfo = decodeDateTime : Decoder ZonedDateTime decodeDateTime = - andThen - (\d -> - case d of - Ok r -> - succeed r - - Err e -> - fail (String.join "\n" <| List.map (Time.Iso8601ErrorMsg.renderText "") e) - ) - (map (Time.Iso8601.toZonedDateTime utc) string) + map (Time.Iso8601.toZonedDateTime utc) string + |> andThen + (\d -> + case d of + Ok r -> + succeed r + + Err e -> + fail (String.join "\n" <| List.map (Time.Iso8601ErrorMsg.renderText "") e) + ) + + +decodeDateInts : Decoder (List ( ZonedDateTime, Int )) +decodeDateInts = + keyValuePairs int + |> andThen + -- traverse using foldl on List + (\pairs -> + case List.foldl (\( d, n ) acc -> Time.Iso8601.toZonedDateTime utc d |> Result.andThen (\date -> Result.map ((::) ( date, n )) acc)) (Ok []) pairs of + Ok r -> + succeed r + + Err e -> + fail (String.join "\n" <| List.map (Time.Iso8601ErrorMsg.renderText "") e) + ) decodePluginError : Decoder PluginError diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/View.elm b/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/View.elm index 05f7f8318e4..b5cfb78cdff 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/View.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/sources/Plugins/View.elm @@ -5,12 +5,14 @@ import Html.Attributes exposing (attribute, checked, class, for, href, id, targe import Html.Attributes.Extra exposing (role) import Html.Events exposing (onCheck, onClick) import List.Extra -import Plugins.ApiCalls exposing (changePluginStatus, installPlugins, removePlugins) +import Plugins.ApiCalls exposing (changePluginStatus, installPlugins, removePlugins, updateIndex) import Plugins.DataTypes exposing (..) import Plugins.JsonEncoder exposing (..) +import String.Extra import Time.DateTime import Time.Iso8601 import Time.ZonedDateTime +import Tuple exposing (first, second) view : Model -> Html Msg @@ -126,7 +128,8 @@ checkAll = actionButtons : Model -> List (Html Msg) actionButtons model = - [ button [ class "btn btn-default me-1", onClick (CallApi (installPlugins model.ui.selected)) ] [ text "Install", i [ class "fa fa-plus-circle ms-1" ] [] ] + [ button [ class "btn btn-primary me-1", onClick (CallApi updateIndex) ] [ i [ class "fa fa-refresh me-1" ] [], text "Check for updates" ] + , button [ class "btn btn-default mx-1", onClick (CallApi (installPlugins model.ui.selected)) ] [ text "Install", i [ class "fa fa-plus-circle ms-1" ] [] ] , button [ class "btn btn-default mx-1", onClick (CallApi (removePlugins model.ui.selected)) ] [ text "Uninstall", i [ class "fa fa-minus-circle ms-1" ] [] ] , button [ class "btn btn-default mx-1", onClick (CallApi (changePluginStatus Enable model.ui.selected)) ] [ text "Enable", i [ class "fa fa-check-circle ms-1" ] [] ] , button [ class "btn btn-default ms-1", onClick (CallApi (changePluginStatus Disable model.ui.selected)) ] [ text "Disable", i [ class "fa fa-ban ms-1" ] [] ] @@ -238,71 +241,97 @@ pluginsSection model = ] -displayMainLicense : Model -> Html Msg -displayMainLicense model = - if model.license == noGlobalLicense then - text "" - - else - let - licensees = - model.license.licensees |> Maybe.map (String.join ", ") |> Maybe.withDefault "-" - - dateOf = - Time.ZonedDateTime.toDateTime >> Time.DateTime.date >> Time.Iso8601.fromDate - - validityPeriod = - case ( model.license.startDate, model.license.endDate ) of - ( Just start, Just end ) -> - "from " ++ dateOf start ++ " to " ++ dateOf end - - _ -> - "-" - - nbNodes = - model.license.maxNodes |> Maybe.map String.fromInt |> Maybe.withDefault "Unlimited" - in - div [ class "main-license" ] - [ div [ attribute "data-plugin" "statusInformation", class "license-card" ] - [ div [ id "license-information", class "license-info" ] - [ h2 [ class "license-info-title" ] [ span [] [ text "License information" ], i [ class "license-icon ion ion-ribbon-b" ] [] ] - , p [ class "license-information-details" ] [ text "The following license information are provided" ] - , div [ class "license-information-details" ] - [ table [ class "table-license" ] - [ tbody [] - [ tr [] [ td [] [ text "Licensee:" ], td [] [ text licensees ] ] - - -- in individual plugin only - -- , tr [] [ td [] [ text "Supported version:" ], td [] [ text "from 0.0.0-0.0.0 to 99.99.0-99.99.0" ] ] - , tr [] [ td [] [ text "Validity period:" ], td [] [ text validityPeriod ] ] - , tr [] [ td [] [ text "Allowed number of nodes:" ], td [] [ text nbNodes ] ] - ] +displayGlobalLicense : LicenseGlobalInfo -> Html Msg +displayGlobalLicense license = + let + licensees = + license.licensees |> Maybe.map (String.join ", ") |> Maybe.withDefault "-" + + dateOf = + Time.ZonedDateTime.toDateTime >> Time.DateTime.date >> Time.Iso8601.fromDate + + -- dates needs to be aggregated by date part of datetime + aggregateDates : List ( Time.ZonedDateTime.ZonedDateTime, number ) -> List ( String, number ) + aggregateDates dates = + dates + |> List.sortBy (first >> dateOf) + |> List.Extra.groupWhile (\x y -> dateOf (first x) == dateOf (first y)) + |> List.map (\( ( d, n ), ns ) -> ( dateOf d, n + (List.map second >> List.sum) ns )) + + displayPluginDates ends = + String.join ", " (List.map (\( d, n ) -> d ++ " (" ++ String.Extra.pluralize " plugin)" " plugins)" n) ends) + + validityPeriod = + case ( license.startDate, license.endDates ) of + ( Just start, Just ends ) -> + "from " ++ dateOf start ++ " to " ++ displayPluginDates (aggregateDates ends) + + _ -> + "-" + + nbNodes = + license.maxNodes |> Maybe.map String.fromInt |> Maybe.withDefault "Unlimited" + in + div [ class "main-license" ] + [ div [ attribute "data-plugin" "statusInformation", class "license-card" ] + [ div [ id "license-information", class "license-info" ] + [ h2 [ class "license-info-title" ] [ span [] [ text "License information" ], i [ class "license-icon ion ion-ribbon-b" ] [] ] + , p [ class "license-information-details" ] [ text "The following license information are provided" ] + , div [ class "license-information-details" ] + [ table [ class "table-license" ] + [ tbody [] + [ tr [] [ td [] [ text "Licensee:" ], td [] [ text licensees ] ] + + -- in individual plugin only + -- , tr [] [ td [] [ text "Supported version:" ], td [] [ text "from 0.0.0-0.0.0 to 99.99.0-99.99.0" ] ] + , tr [] [ td [] [ text "Validity period:" ], td [] [ text validityPeriod ] ] + , tr [] [ td [] [ text "Allowed number of nodes:" ], td [] [ text nbNodes ] ] ] ] ] ] ] + ] + + +displaySettingError : Model -> String -> String -> Html Msg +displaySettingError model message details = + div [ class "callout-fade callout-warning overflow-scroll" ] + [ p [] [ i [ class "fa fa-warning" ] [], text message ] + , p [] [ a [ target "_blank", href (model.contextPath ++ "/secure/administration/settings") ] [ text "Open Rudder account settings", i [ class "fa fa-external-link ms-1" ] [] ] ] + , p [] + [ a [ class "btn btn-default", attribute "data-bs-toggle" "collapse", href "#collapseSettingsError", role "button", attribute "aria-expanded" "false", attribute "aria-controls" "collapseSettingsError" ] + [ text "See details" ] + ] + , div [ class "collapse", id "collapseSettingsError" ] + [ div [ class "card card-body" ] + [ pre [ class "command-output" ] + [ text details + ] + ] + ] + ] + + +displayMainLicense : Model -> Html Msg +displayMainLicense model = + case model.license of + Nothing -> + displaySettingError model "No license found" ("Installed plugins : " ++ String.join "," (List.map .id model.plugins)) + + Just license -> + if license == noGlobalLicense then + displaySettingError model "Empty license found" ("Installed plugins : " ++ String.join "," (List.map .id model.plugins)) + + else + displayGlobalLicense license displaySettingsErrorOrHtml : Model -> Html Msg -> Html Msg displaySettingsErrorOrHtml model orHtml = case model.ui.settingsError of Just ( message, details ) -> - div [ class "callout-fade callout-warning overflow-scroll" ] - [ p [] [ i [ class "fa fa-warning" ] [], text message ] - , p [] [ a [ target "_blank", href (model.contextPath ++ "/secure/administration/settings") ] [ text "Open Rudder account settings", i [ class "fa fa-external-link ms-1" ] [] ] ] - , p [] - [ a [ class "btn btn-default", attribute "data-bs-toggle" "collapse", href "#collapseSettingsError", role "button", attribute "aria-expanded" "false", attribute "aria-controls" "collapseSettingsError" ] - [ text "See details" ] - ] - , div [ class "collapse", id "collapseSettingsError" ] - [ div [ class "card card-body" ] - [ pre [ class "command-output" ] - [ text details - ] - ] - ] - ] + displaySettingError model message details Nothing -> orHtml diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala index 7511754e621..6c9026afc67 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala @@ -922,13 +922,7 @@ class Boot extends Loggable { Nil case Right(plugins) => val scalaKeys = scalaPlugins.keySet - // log parsing errors - plugins.collect { case Left(e) => e }.foreach { e => - PluginLogger.error( - s"Error when parsing plugin information from index file '${RudderConfig.jsonPluginDefinition.index.pathAsString}': ${e.fullMsg}" - ) - } - plugins.collect { case Right(p) if (!scalaKeys.contains(p.name) && p.jars.isEmpty) => p }.map { p => + plugins.collect { case p if (!scalaKeys.contains(p.name) && p.jars.isEmpty) => p }.map { p => val sn = p.name.replace("rudder-plugin-", "") new RudderPluginDef { override def displayName = sn.capitalize diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/RudderPlugin.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/RudderPlugin.scala index ad5d4b03082..aa658594107 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/RudderPlugin.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/RudderPlugin.scala @@ -370,16 +370,14 @@ class PluginSystemServiceImpl( pluginDefs: => Map[PluginName, RudderPluginDef], rudderFullVersion: String ) extends PluginSystemService { + override def updateIndex(): IOResult[Option[CredentialError]] = { + rudderPackageService.update() + } - override def list(): IOResult[Either[CredentialError, Chunk[JsonPluginSystemDetails]]] = { - for { - optError <- rudderPackageService.updateBase() - rudderPackagePlugins <- rudderPackageService.listAllPlugins() - } yield { - optError.toLeft( - rudderPackagePlugins.map(mergePluginDef(_)) - ) - } + override def list(): IOResult[Chunk[JsonPluginSystemDetails]] = { + rudderPackageService + .listAllPlugins() + .map(_.map(mergePluginDef(_))) } override def install(plugins: Chunk[PluginId]): IOResult[Unit] = { diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/RudderPluginJson.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/RudderPluginJson.scala index 57dc5bcce38..2142a7f0b60 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/RudderPluginJson.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/RudderPluginJson.scala @@ -39,11 +39,11 @@ package com.normation.plugins import better.files.File import com.normation.errors.* +import io.scalaland.chimney.Transformer +import io.scalaland.chimney.syntax.* import java.nio.charset.StandardCharsets -import net.liftweb.json.* import org.joda.time.DateTime -import org.joda.time.format.ISODateTimeFormat -import zio.* +import zio.json.* import zio.syntax.* /* @@ -52,17 +52,38 @@ import zio.syntax.* */ // this is only used internally but liftweb can't hand it if not top level -final protected case class JsonPluginFile(plugins: Map[String, JValue]) +final protected case class JsonPluginFile(plugins: Map[String, JsonPluginRaw]) +protected object JsonPluginFile { + implicit val decoder: JsonDecoder[JsonPluginFile] = DeriveJsonDecoder.gen[JsonPluginFile] +} -// this is only used internally but liftweb can't hand it if not top level +// json format of plugin, optional fields are there to obtain empty lists when mapping none +@jsonMemberNames(KebabCase) final protected case class JsonPluginRaw( - name: String, - version: String, - files: List[String], - `jar-files`: List[String], - `build-commit`: String, - `build-date`: String + name: String, + version: PluginVersion, + files: Option[List[String]], + jarFiles: Option[List[String]], + buildCommit: String, + buildDate: DateTime ) +protected object JsonPluginRaw { + import com.normation.utils.DateFormaterService.json.* + implicit val pluginVersionDecoder: JsonDecoder[PluginVersion] = { + JsonDecoder[String].mapOrFail(version => + PluginVersion.from(version).toRight(s"Version is not a valid plugin version: ${version}") + ) + } + implicit val decoder: JsonDecoder[JsonPluginRaw] = DeriveJsonDecoder.gen[JsonPluginRaw] + + implicit val transformer: Transformer[JsonPluginRaw, JsonPluginDef] = { + Transformer + .define[JsonPluginRaw, JsonPluginDef] + .withFieldComputed(_.files, _.files.getOrElse(List.empty)) + .withFieldComputed(_.jars, _.jarFiles.getOrElse(List.empty)) + .buildTransformer + } +} final case class JsonPluginDef( name: String, @@ -78,6 +99,7 @@ final case class JsonPluginDef( * By convention, in Rudder, path = /var/rudder/packages/index.json */ class ReadPluginPackageInfo(path: String) { + import ReadPluginPackageInfo.* val index: File = File(path) @@ -93,40 +115,18 @@ class ReadPluginPackageInfo(path: String) { } } - def parseFile(json: String): IOResult[JsonPluginFile] = { - IOResult.attempt { - implicit val formats = net.liftweb.json.DefaultFormats - JsonParser.parse(json).extract[JsonPluginFile] - } - } - - def decodeOne(plugin: JValue): IOResult[JsonPluginDef] = { - implicit val formats = net.liftweb.json.DefaultFormats - for { - p <- IOResult.attempt(plugin.extract[JsonPluginRaw]) - date <- IOResult.attempt(DateTime.parse(p.`build-date`, ISODateTimeFormat.dateTimeNoMillis())) - version <- PluginVersion.from(p.version).notOptional(s"Version for '${p.name}' is not a valid plugin version: ${p.version}") - } yield { - JsonPluginDef(p.name, version, p.files, p.`jar-files`, p.`build-commit`, date) - } - } - - def decode(plugin: JsonPluginFile): IOResult[List[Either[RudderError, JsonPluginDef]]] = { - ZIO.foreach(plugin.plugins.toList) { - case (name, jvalue) => - decodeOne(jvalue).mapError(err => Chained(s"Error when decoding plugin information for entry '${name}'", err)).either - } - } - - def parseJson(json: String): IOResult[List[Either[RudderError, JsonPluginDef]]] = { - parseFile(json).flatMap(decode) - } - - def getInfo(): IOResult[List[Either[RudderError, JsonPluginDef]]] = { + def getInfo(): IOResult[List[JsonPluginDef]] = { IOResult.attempt(index.exists).flatMap { exists => - if (exists) check *> readIndex().flatMap(parseJson) - else List().succeed + if (exists) { + check *> readIndex() + .flatMap(parseJsonPluginFileDefs(_).toIO) + } else List.empty.succeed } } } + +object ReadPluginPackageInfo { + def parseJsonPluginFileDefs(s: String): Either[String, List[JsonPluginDef]] = + s.fromJson[JsonPluginFile].map(_.plugins.values.toList.map(_.transformInto[JsonPluginDef])) +} diff --git a/webapp/sources/rudder/rudder-web/src/test/resources/plugins-index.json b/webapp/sources/rudder/rudder-web/src/test/resources/plugins-index.json new file mode 100644 index 00000000000..92422ae2aa7 --- /dev/null +++ b/webapp/sources/rudder/rudder-web/src/test/resources/plugins-index.json @@ -0,0 +1,531 @@ +{ + "plugins": { + "rudder-plugin-system-updates": { + "files": [ + "/opt/rudder/share/plugins/system-updates/", + "/opt/rudder/share/plugins/system-updates/system-update-schema.sql", + "/opt/rudder/share/plugins/system-updates/system-updates.jar", + "/opt/rudder/share/plugins/system-updates/technique/", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/metadata.xml", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/software_list.json.st", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/systemUpdateCampaign.st", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Get-SystemUpdateDateFile.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Get-SystemUpdateLock.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Install-AvailableUpdates.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Format-Exception.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Log-Campaign.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/ConvertTo-FriendlyWUpdateHResultString.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/ConvertFrom-UnixTimeStamp.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Rudder-SystemUpdatePowershellExecution.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Get-SystemUpdateSent.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Reboot-PostUpgrade.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/ConvertTo-UnixTimeStamp.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Run-CampaignHooks.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/System-UpdateCampaignCore.ps1.st", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Set-SystemUpdateReport.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Get-Now.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Get-RawAvailableUpdates.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Get-SoftwareList.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/ConvertFrom-Hexadecimal.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Set-SystemUpdateLock.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Set-SystemUpdateInventoryFlag.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Set-SystemUpdateUpdated.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Get-SystemUpdateFilePath.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Set-SystemUpdateSchedule.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Get-SystemUpdateScheduleDone.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Get-SystemUpdateFinished.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Set-SystemUpdateSent.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Format-Update.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Download-Update.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Set-SystemUpdateReportOutput.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Get-SplayedStart.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/windows/Download-Updates.ps1", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/modules/", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/modules/dev-requirements.txt", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/modules/system_update.py", + "/opt/rudder/share/plugins/system-updates/technique/systemUpdateCampaign/1.0/systemUpdateCampaign.ps1.st" + ], + "type": "plugin", + "name": "rudder-plugin-system-updates", + "version": "8.2.4-1.17", + "description": "Available upgrades management and upgrade campaigns", + "build-date": "2025-01-13T09:43:16+00:00", + "build-commit": "948836f019635cbeea919973a48dc0ef91e90ac1", + "content": { + "files.txz": "/opt/rudder/share/plugins" + }, + "jar-files": [ + "/opt/rudder/share/plugins/system-updates/system-updates.jar" + ] + }, + "rudder-plugin-security-benchmarks": { + "files": [ + "/opt/rudder/share/plugins/security-benchmarks/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/rsc_01/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/rsc_01.yml", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section2/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section2/1.0/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section2/1.0/metadata.xml", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section2/1.0/technique.ps1", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section2/1.0/technique.cf", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section7/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section7/1.0/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section7/1.0/metadata.xml", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section7/1.0/technique.ps1", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section7/1.0/technique.cf", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/category.xml", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section4/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section4/1.0/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section4/1.0/metadata.xml", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section4/1.0/technique.ps1", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section4/1.0/technique.cf", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section8/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section8/1.0/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section8/1.0/metadata.xml", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section8/1.0/technique.ps1", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section8/1.0/technique.cf", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section9/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section9/1.0/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section9/1.0/metadata.xml", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section9/1.0/technique.ps1", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section9/1.0/technique.cf", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section1/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section1/1.0/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section1/1.0/metadata.xml", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section1/1.0/technique.ps1", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section1/1.0/technique.cf", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section3/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section3/1.0/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section3/1.0/metadata.xml", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section3/1.0/technique.ps1", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section3/1.0/technique.cf", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section6/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section6/1.0/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section6/1.0/metadata.xml", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section6/1.0/technique.ps1", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section6/1.0/technique.cf", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section5/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section5/1.0/", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section5/1.0/metadata.xml", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section5/1.0/technique.ps1", + "/opt/rudder/share/plugins/security-benchmarks/rsc_01/techniques/rsc_01_section5/1.0/technique.cf", + "/opt/rudder/share/plugins/security-benchmarks/security-benchmarks.jar" + ], + "type": "plugin", + "name": "rudder-plugin-security-benchmarks", + "version": "8.2.4-1.0", + "description": "Assess and enforce security benchmarks", + "build-date": "2025-01-13T09:42:54+00:00", + "build-commit": "948836f019635cbeea919973a48dc0ef91e90ac1", + "content": { + "files.txz": "/opt/rudder/share/plugins" + }, + "jar-files": [ + "/opt/rudder/share/plugins/security-benchmarks/security-benchmarks.jar" + ] + }, + "rudder-plugin-change-validation": { + "files": [ + "/opt/rudder/share/plugins/change-validation/", + "/opt/rudder/share/plugins/change-validation/change-validation.jar", + "/opt/rudder/share/plugins/change-validation/deployment-mail.template", + "/opt/rudder/share/plugins/change-validation/change-validation.conf", + "/opt/rudder/share/plugins/change-validation/cancelled-mail.template", + "/opt/rudder/share/plugins/change-validation/validation-mail.template", + "/opt/rudder/share/plugins/change-validation/deployed-mail.template", + "/opt/rudder/share/plugins/change-validation/change-validation-schema.sql" + ], + "type": "plugin", + "name": "rudder-plugin-change-validation", + "version": "8.2.4-2.4", + "description": "Built-in change review interface", + "build-date": "2025-01-13T10:22:43+00:00", + "build-commit": "", + "content": { + "files.txz": "/opt/rudder/share/plugins" + }, + "jar-files": [ + "/opt/rudder/share/plugins/change-validation/change-validation.jar" + ] + }, + "rudder-plugin-reporting": { + "files": [ + "/opt/rudder/share/plugins/reporting/", + "/opt/rudder/share/plugins/reporting/reporting-schema.sql", + "/opt/rudder/share/plugins/reporting/reporting.jar" + ], + "type": "plugin", + "name": "rudder-plugin-reporting", + "version": "8.2.4-2.1", + "description": "Generate PDF reports of historized compliance", + "build-date": "2025-01-13T09:43:46+00:00", + "build-commit": "948836f019635cbeea919973a48dc0ef91e90ac1", + "content": { + "files.txz": "/opt/rudder/share/plugins" + }, + "jar-files": [ + "/opt/rudder/share/plugins/reporting/reporting.jar" + ] + }, + "rudder-plugin-auth-backends": { + "files": [ + "/opt/rudder/share/plugins/auth-backends/", + "/opt/rudder/share/plugins/auth-backends/auth-backends.jar" + ], + "type": "plugin", + "name": "rudder-plugin-auth-backends", + "version": "8.2.4-2.7", + "description": "External authentication (OIDC/AD/LDAP)", + "build-date": "2025-01-13T09:40:25+00:00", + "build-commit": "", + "content": { + "files.txz": "/opt/rudder/share/plugins" + }, + "jar-files": [ + "/opt/rudder/share/plugins/auth-backends/auth-backends.jar" + ] + }, + "rudder-plugin-dsc": { + "files": [ + "/opt/rudder/share/plugins/dsc/", + "/opt/rudder/share/plugins/dsc/dsc.jar", + "/opt/rudder/etc/hooks.d/", + "/opt/rudder/etc/hooks.d/policy-generation-pre-start/", + "/opt/rudder/etc/hooks.d/policy-generation-pre-start/prevent-obsolete-syntax-on-windows", + "/opt/rudder/etc/hooks.d/policy-generation-node-finished/", + "/opt/rudder/etc/hooks.d/policy-generation-node-finished/91-compress-dsc-policies", + "/var/rudder/configuration-repository/ncf/", + "/var/rudder/configuration-repository/ncf/30_generic_methods/", + "/var/rudder/configuration-repository/ncf/30_generic_methods/user_status.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/registry_key_present.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/windows_component_present.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/powershell_execution.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/package_state_windows.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/user_password_clear.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/windows_hotfix_absent.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/registry_entry_present.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/dsc_from_configuration.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/permissions_ntfs.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/windows_component_absent.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/audit_from_powershell_execution.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/windows_hotfix_present.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/dsc_apply.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/registry_key_absent.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/service_status.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/registry_entry_absent.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/dsc_built_in_resource.cf", + "/var/rudder/configuration-repository/ncf/30_generic_methods/dsc_mof_file_apply.cf", + "/var/rudder/configuration-repository/techniques/", + "/var/rudder/configuration-repository/techniques/applications/", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/1.0/", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/1.0/windowsSoftware.ps1.st", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/1.0/metadata.xml", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/2.0/", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/2.0/windowsSoftware.ps1.st", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/2.0/metadata.xml", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/2.1/", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/2.1/windowsSoftware.ps1.st", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/2.1/metadata.xml", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/2.2/", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/2.2/getAppVersion.ps1", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/2.2/windowsSoftware.ps1.st", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/2.2/isVersionCorrect.ps1", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/2.2/metadata.xml", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/2.2/doInstall.ps1", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/2.2/isInstalled.ps1", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/2.2/getVersionFromRegistry.ps1", + "/var/rudder/configuration-repository/techniques/applications/windowsSoftware/2.2/windowsSoftwareCommandDetection.ps1", + "/var/rudder/configuration-repository/techniques/systemSettings/", + "/var/rudder/configuration-repository/techniques/systemSettings/misc/", + "/var/rudder/configuration-repository/techniques/systemSettings/misc/registryManagement/", + "/var/rudder/configuration-repository/techniques/systemSettings/misc/registryManagement/3.0/", + "/var/rudder/configuration-repository/techniques/systemSettings/misc/registryManagement/3.0/metadata.xml", + "/var/rudder/configuration-repository/techniques/systemSettings/misc/registryManagement/3.0/registryEdition.ps1.st", + "/var/rudder/configuration-repository/techniques/systemSettings/misc/registryManagement/4.0/", + "/var/rudder/configuration-repository/techniques/systemSettings/misc/registryManagement/4.0/metadata.xml", + "/var/rudder/configuration-repository/techniques/systemSettings/misc/registryManagement/4.0/registryEdition.ps1.st", + "/var/rudder/configuration-repository/dsc/", + "/var/rudder/configuration-repository/dsc/ncf/", + "/var/rudder/configuration-repository/dsc/ncf/10_ncf_internals/", + "/var/rudder/configuration-repository/dsc/ncf/10_ncf_internals/End-RudderPolicyRun.ps1", + "/var/rudder/configuration-repository/dsc/ncf/10_ncf_internals/Remove-RudderVar.ps1", + "/var/rudder/configuration-repository/dsc/ncf/10_ncf_internals/ncf_lib.ps1", + "/var/rudder/configuration-repository/dsc/ncf/10_ncf_internals/Add-RudderVar.ps1", + "/var/rudder/configuration-repository/dsc/ncf/10_ncf_internals/SafeGet-ChildItem.ps1", + "/var/rudder/configuration-repository/dsc/ncf/10_ncf_internals/Compare-RudderData.ps1", + "/var/rudder/configuration-repository/dsc/ncf/10_ncf_internals/convertPSObjectToHashtable.ps1", + "/var/rudder/configuration-repository/dsc/ncf/10_ncf_internals/leagcySystemConditionsLoader.ps1", + "/var/rudder/configuration-repository/dsc/ncf/10_ncf_internals/Merge-RudderData.ps1", + "/var/rudder/configuration-repository/dsc/ncf/10_ncf_internals/rudderLibLoader.ps1", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/tr/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/tr/FSharp.Core.resources.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/FParsec.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/pt-BR/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/pt-BR/FSharp.Core.resources.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/FSharp.Core.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/rudderLib.sln", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/Newtonsoft.Json.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/.gitignore", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/Serilog.Sinks.File.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/Serilog.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/zh-Hans/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/zh-Hans/FSharp.Core.resources.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/zh-Hant/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/zh-Hant/FSharp.Core.resources.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/de/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/de/FSharp.Core.resources.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/Library/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/Library/Condition.fs", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/Library/MethodResult.fs", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/Library/ReportStatus.fs", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/Library/Report.fs", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/Library/PolicyMode.fs", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/Library/DiscriminatedUnionHelper.fs", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/Library/Logger.fs", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/Library/MethodStatus.fs", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/Library/Datastate.fs", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/Library/InfoLog.fs", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/Library/Library.fsproj", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/Library/Utils.fs", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/Library/ApplyResult.fs", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/src/Library/Context.fs", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/ru/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/ru/FSharp.Core.resources.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/Makefile", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/pl/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/pl/FSharp.Core.resources.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/es/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/es/FSharp.Core.resources.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/Library.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/cs/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/cs/FSharp.Core.resources.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/it/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/it/FSharp.Core.resources.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/Library.pdb", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/FParsecCS.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/Nustache.Core.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/System.ValueTuple.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/ko/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/ko/FSharp.Core.resources.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/fr/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/fr/FSharp.Core.resources.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/Serilog.Sinks.Console.dll", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/ja/", + "/var/rudder/configuration-repository/dsc/ncf/rudderLib/ja/FSharp.Core.resources.dll", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/service_ensure_stopped.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/condition_from_string_match.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/user_absent.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/_check_compliance.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/directory_create.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/registry_key_present.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/service_restart.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/variable_dict_from_file.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_download.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_from_http_server.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/service_status.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/variable_string.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/dsc_built_in_resource.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/service_stopped.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/service_disabled_at_boot.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/directory_absent.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_remove.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/service_enabled.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_lines_present.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_ensure_lines_present.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_content.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/dsc_mof_file_apply.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/condition_from_command.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/dsc_apply.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/registry_entry_present.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/service_ensure_started_at_boot.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/service_started.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/command_execution.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_block_present.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/user_present.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/user_password_clear.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/registry_entry_absent.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_absent.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_from_template.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_report_content_tail.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/windows_component_present.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/service_ensure_running.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/rudder_inventory_trigger.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/windows_hotfix_present.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/directory_present.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/condition_from_variable_match.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_copy_from_local_source.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/audit_from_powershell_execution.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_create.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/variable_string_from_file.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/condition_from_variable_existence.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/windows_hotfix_absent.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_key_value_present_option.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/user_status.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/service_ensure_disabled_at_boot.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/permissions_ntfs.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/windows_component_absent.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/variable_dict.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/variable_string_from_command.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_lines_absent.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/variable_dict_merge.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/package_state_windows.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_from_template_mustache.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_enforce_content.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_from_local_source.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/powershell_execution.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/dsc_from_configuration.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_present.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/file_from_shared_folder.ps1", + "/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/registry_key_absent.ps1", + "/var/rudder/configuration-repository/techniques/system/dsc-common/", + "/var/rudder/configuration-repository/techniques/system/dsc-common/1.0/", + "/var/rudder/configuration-repository/techniques/system/dsc-common/1.0/inventory.ps1", + "/var/rudder/configuration-repository/techniques/system/dsc-common/1.0/rudder-directives.st", + "/var/rudder/configuration-repository/techniques/system/dsc-common/1.0/rudder-system-directives.st", + "/var/rudder/configuration-repository/techniques/system/dsc-common/1.0/template/", + "/var/rudder/configuration-repository/techniques/system/dsc-common/1.0/template/Rudder-Agent.xml.mustache", + "/var/rudder/configuration-repository/techniques/system/dsc-common/1.0/template/Rudder-Inventory.xml.mustache", + "/var/rudder/configuration-repository/techniques/system/dsc-common/1.0/prerun-check.ps1", + "/var/rudder/configuration-repository/techniques/system/dsc-common/1.0/execution_scheduler.ps1", + "/var/rudder/configuration-repository/techniques/system/dsc-common/1.0/metadata.xml", + "/var/rudder/configuration-repository/techniques/system/dsc-common/1.0/variables.ps1", + "/var/rudder/configuration-repository/techniques/system/dsc-common/1.0/reporting-http.ps1", + "/var/rudder/configuration-repository/techniques/system/dsc-common/1.0/rudder-vars.st", + "/var/rudder/configuration-repository/techniques/system/dsc-common/1.0/logging.ps1" + ], + "type": "plugin", + "name": "rudder-plugin-dsc", + "version": "8.2.4-2.8", + "description": "Support for Windows nodes", + "build-date": "2025-01-13T09:41:12+00:00", + "depends": { + "binary": [ + "zip" + ] + }, + "build-commit": "948836f019635cbeea919973a48dc0ef91e90ac1", + "content": { + "dsc-common.txz": "/var/rudder/configuration-repository/techniques/system", + "techniques.txz": "/var/rudder/configuration-repository", + "agent-policy.txz": "/var/rudder/configuration-repository", + "ncf.txz": "/var/rudder/configuration-repository", + "hooks.txz": "/opt/rudder/etc", + "files.txz": "/opt/rudder/share/plugins" + }, + "jar-files": [ + "/opt/rudder/share/plugins/dsc/dsc.jar" + ] + }, + "rudder-plugin-multi-tenants": { + "files": [ + "/opt/rudder/share/plugins/multi-tenants/", + "/opt/rudder/share/plugins/multi-tenants/multi-tenants.jar" + ], + "type": "plugin", + "name": "rudder-plugin-multi-tenants", + "version": "8.2.4-1.1", + "description": "Attribute nodes to different user teams", + "build-date": "2025-01-13T09:43:02+00:00", + "build-commit": "948836f019635cbeea919973a48dc0ef91e90ac1", + "content": { + "files.txz": "/opt/rudder/share/plugins" + }, + "jar-files": [ + "/opt/rudder/share/plugins/multi-tenants/multi-tenants.jar" + ] + }, + "rudder-plugin-api-authorizations": { + "files": [ + "/opt/rudder/share/plugins/api-authorizations/", + "/opt/rudder/share/plugins/api-authorizations/api-authorizations.jar" + ], + "type": "plugin", + "name": "rudder-plugin-api-authorizations", + "version": "8.2.4-2.2", + "description": "Fine-grained API ACLs", + "build-date": "2025-01-13T09:40:14+00:00", + "build-commit": "", + "content": { + "files.txz": "/opt/rudder/share/plugins" + }, + "jar-files": [ + "/opt/rudder/share/plugins/api-authorizations/api-authorizations.jar" + ] + }, + "rudder-plugin-branding": { + "files": [ + "/opt/rudder/share/plugins/branding/", + "/opt/rudder/share/plugins/branding/branding.jar" + ], + "type": "plugin", + "name": "rudder-plugin-branding", + "version": "8.2.4-2.2", + "description": "Customize the Web UI colors and logo", + "build-date": "2025-01-13T09:40:43+00:00", + "build-commit": "", + "content": { + "files.txz": "/opt/rudder/share/plugins" + }, + "jar-files": [ + "/opt/rudder/share/plugins/branding/branding.jar" + ] + }, + "rudder-plugin-openscap": { + "files": [ + "/opt/rudder/share/plugins/openscap/openscap_technique.zip", + "/opt/rudder/share/plugins/openscap/openscap/", + "/opt/rudder/share/plugins/openscap/openscap/openscap.jar" + ], + "type": "plugin", + "name": "rudder-plugin-openscap", + "version": "8.2.4-2.2", + "description": "Run OpenSCAP benchmarks and collect results", + "build-date": "2025-01-13T10:21:39+00:00", + "depends": { + "apt": [ + "rudder-api-client" + ], + "rpm": [ + "rudder-api-client" + ] + }, + "build-commit": "", + "content": { + "files.txz": "/opt/rudder/share/plugins/openscap/" + }, + "jar-files": [ + "/opt/rudder/share/plugins/openscap/openscap/openscap.jar" + ] + }, + "rudder-plugin-cve": { + "files": [ + "/opt/rudder/share/plugins/cve/", + "/opt/rudder/share/plugins/cve/check_cve.json", + "/opt/rudder/share/plugins/cve/cve.jar", + "/opt/rudder/share/plugins/cve/update_cve.json", + "/opt/rudder/share/plugins/cve/cve-schema.sql" + ], + "type": "plugin", + "name": "rudder-plugin-cve", + "version": "8.2.4-2.10", + "description": "Manage known vulnerabilities in system components", + "build-date": "2025-01-13T09:43:53+00:00", + "build-commit": "948836f019635cbeea919973a48dc0ef91e90ac1", + "content": { + "files.txz": "/opt/rudder/share/plugins" + }, + "jar-files": [ + "/opt/rudder/share/plugins/cve/cve.jar" + ] + } + } +} \ No newline at end of file diff --git a/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginJsonTest.scala b/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginJsonTest.scala index 7c721331f5d..8d6775d796f 100644 --- a/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginJsonTest.scala +++ b/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginJsonTest.scala @@ -37,6 +37,7 @@ package com.normation.plugins +import better.files.Resource import com.normation.utils.ParseVersion import com.normation.utils.Version import com.normation.zio.* @@ -164,16 +165,14 @@ class RudderPluginJsonTest extends Specification { ) ) - val packageService = new ReadPluginPackageInfo("/tmp/foo") - "Plugins JSON service" should { "be able to read json file format" in { - val all = packageService.parseJson(index_json).runNow - val success = all.collect { case Right(x) => x } - val errors = all.collect { case Left(x) => x } + ReadPluginPackageInfo.parseJsonPluginFileDefs(index_json) must beRight(containTheSameElementsAs(expected)) + } - (errors must beEmpty) and - (success must containTheSameElementsAs(expected)) + "be able to read all plugins" in { + val read = new ReadPluginPackageInfo(Resource.getUrl("plugins-index.json").getPath()) + read.getInfo().either.runNow must beRight(haveSize[List[?]](11)) } } }