Skip to content

Commit

Permalink
Fixes #21383: Add logic to handle campaign within Rudder
Browse files Browse the repository at this point in the history
  • Loading branch information
VinceMacBuche committed Jul 11, 2022
1 parent 53f0b88 commit d855f09
Show file tree
Hide file tree
Showing 28 changed files with 1,282 additions and 160 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,12 @@ ALTER TABLE statusupdate set (autovacuum_vacuum_threshold = 0);
* end
*************************************************************************************
*/


CREATE TABLE CampaignEvent (
campaignId text
, eventid text PRIMARY KEY
, state text
, startDate timestamp with time zone NOT NULL
, endDate timestamp with time zone NOT NULL
);
Original file line number Diff line number Diff line change
Expand Up @@ -418,26 +418,19 @@ trait RudderJsonDecoders {

}

/*
* This last class provides utility methods to get JsonQuery objects from the request.
* We want to get ride of RestExtractorService but for now, we keep it for the parameter parts.
*/
class ZioJsonExtractor(queryParser: CmdbQueryParser with JsonQueryLexer) {
import JsonResponseObjects._
import JsonQueryObjects._
import implicits._
object ZioJsonExtractor {

/**
* Parse request body as JSON, and decode it as type `A`.
* This is the root method to transform a JSON query into a Rest object.
*/
def parseJson[A](req: Req)(implicit decoder: JsonDecoder[A]): PureResult[A] = {
if(req.json_?) {
// copied from `Req.forcedBodyAsJson`
def r = """; *charset=(.*)""".r
def r2 = """[^=]*$""".r
// copied from `Req.forcedBodyAsJson`
def r = """; *charset=(.*)""".r
def r2 = """[^=]*$""".r
def charset: String = req.contentType.flatMap(ct => r.findFirstIn(ct).flatMap(r2.findFirstIn)).getOrElse("UTF-8")
// end copy
// end copy

req.body match {
case eb: EmptyBox => Left(Unexpected((eb ?~! "error when accessing request body").messageChain))
Expand All @@ -447,6 +440,17 @@ class ZioJsonExtractor(queryParser: CmdbQueryParser with JsonQueryLexer) {
Left(Unexpected("Cannot parse non-JSON request as JSON; please check content-type."))
}
}
}

/*
* This last class provides utility methods to get JsonQuery objects from the request.
* We want to get ride of RestExtractorService but for now, we keep it for the parameter parts.
*/
class ZioJsonExtractor(queryParser: CmdbQueryParser with JsonQueryLexer) {
import JsonResponseObjects._
import JsonQueryObjects._
import implicits._
import ZioJsonExtractor.parseJson

/**
* Utilities to extract values from params Map
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ class AutomaticReportLogger(
val highest = reportsRepository.getHighestId()
logger.trace(s"***** highest report id = ${highest} and last processed id = ${lastId}")
highest match {
case Full(currentId) if (currentId > lastId) =>
logReportsBetween(lastId, currentId)
case Full(currentId) if (currentId > lastId.getOrElse(0L)) =>
logReportsBetween(lastId.getOrElse(0L), currentId)

case _ =>
logger.trace("***** no reports to log")
Expand Down Expand Up @@ -182,7 +182,7 @@ class AutomaticReportLogger(
, allNodes: Map[NodeId, NodeInfo], rules: Map[RuleId, Rule], directives: FullActiveTechniqueCategory
): Box[Long] = {
for {
reports <- reportsRepository.getReportsByKindBeetween(fromId, maxId, batchSize, reportsKind)
reports <- reportsRepository.getReportsByKindBetween(fromId, Some(maxId), batchSize, reportsKind)
} yield {
//when we get an empty here, it means that we don't have more non-compliant report
//in the interval, just return the max id
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
*************************************************************************************
* Copyright 2022 Normation SAS
*************************************************************************************
*
* This file is part of Rudder.
*
* Rudder is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In accordance with the terms of section 7 (7. Additional Terms.) of
* the GNU General Public License version 3, the copyright holders add
* the following Additional permissions:
* Notwithstanding to the terms of section 5 (5. Conveying Modified Source
* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU General
* Public License version 3, when you create a Related Module, this
* Related Module is not considered as a part of the work and may be
* distributed under the license agreement of your choice.
* A "Related Module" means a set of sources files including their
* documentation that, without modification of the Source Code, enables
* supplementary functions or services in addition to those offered by
* the Software.
*
* Rudder is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Rudder. If not, see <http://www.gnu.org/licenses/>.
*
*************************************************************************************
*/

package com.normation.rudder.campaigns

import com.normation.errors.IOResult
import com.normation.rudder.campaigns.CampaignEventState.Scheduled
import com.normation.rudder.db.Doobie
import doobie.Read
import doobie.Write
import doobie.implicits._
import doobie.implicits.javasql._
import doobie.implicits.toSqlInterpolator
import org.joda.time.DateTime
import zio.interop.catz._

import java.sql.Timestamp


trait CampaignEventRepository {
def getAllActiveCampaignEvents() : IOResult[List[CampaignEvent]]
def get(campaignEventId: CampaignEventId) : IOResult[CampaignEvent]
def getEventsForCampaign(campaignId: CampaignId, state: Option[CampaignEventState]) : IOResult[List[CampaignEvent]]
def saveCampaignEvent(c : CampaignEvent) : IOResult[CampaignEvent]
}

class CampaignEventRepositoryImpl(doobie: Doobie) extends CampaignEventRepository {

import doobie._


implicit val stateWrite : Write[CampaignEventState] = Write[String].contramap(_.value)

implicit val eventWrite: Write[CampaignEvent] =
Write[(String,String,CampaignEventState,Timestamp,Timestamp)].contramap{
case event => (event.id.value,event.campaignId.value, event.state , new java.sql.Timestamp(event.start.getMillis), new java.sql.Timestamp(event.end.getMillis))
}

implicit val eventRead : Read[CampaignEvent] =
Read[(String,String,String,Timestamp,Timestamp)].map {
d : (String,String,String,Timestamp,Timestamp) =>
CampaignEvent(
CampaignEventId(d._1)
, CampaignId(d._2)
, CampaignEventState.parse(d._3).getOrElse(Scheduled)
, new DateTime(d._4.getTime())
, new DateTime(d._5.getTime())
)
}

def getAllActiveCampaignEvents(): IOResult[List[CampaignEvent]] = {
val q = sql"select eventId, campaignId, state, startDate, endDate from CampaignEvent where state != 'finished' and state != 'skipped'"
transactIOResult(s"error when getting active campaign events")(xa => q.query[CampaignEvent].to[List].transact(xa))
}


def get(id : CampaignEventId): IOResult[CampaignEvent] = {
val q = sql"select eventId, campaignId, state, startDate, endDate from CampaignEvent where eventId = '${id.value}'"
transactIOResult(s"error when getting campaign event with id ${id.value}")(xa => q.query[CampaignEvent].unique.transact(xa))
}



def saveCampaignEvent(c: CampaignEvent): IOResult[CampaignEvent] = {
import doobie._
val query =
sql"""insert into campaignEvent (eventId, campaignId, state, startDate, endDate) values (${c})
| ON CONFLICT (eventId) DO UPDATE
| SET state = ${c.state}; """.stripMargin

transactIOResult(s"error when inserting event with id ${c.campaignId.value}")(xa => query.update.run.transact(xa)).map(_ => c)
}

def getEventsForCampaign(campaignId: CampaignId, state: Option[CampaignEventState]): IOResult[List[CampaignEvent]] = {
val q = sql"select eventId, campaignId, state, startDate, endDate from CampaignEvent where campaignId = ${campaignId.value}"
transactIOResult(s"error when getting campaign events for campaign ${campaignId.value}")(xa => q.query.to[List].transact(xa))
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
*************************************************************************************
* Copyright 2022 Normation SAS
*************************************************************************************
*
* This file is part of Rudder.
*
* Rudder is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In accordance with the terms of section 7 (7. Additional Terms.) of
* the GNU General Public License version 3, the copyright holders add
* the following Additional permissions:
* Notwithstanding to the terms of section 5 (5. Conveying Modified Source
* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU General
* Public License version 3, when you create a Related Module, this
* Related Module is not considered as a part of the work and may be
* distributed under the license agreement of your choice.
* A "Related Module" means a set of sources files including their
* documentation that, without modification of the Source Code, enables
* supplementary functions or services in addition to those offered by
* the Software.
*
* Rudder is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Rudder. If not, see <http://www.gnu.org/licenses/>.
*
*************************************************************************************
*/

package com.normation.rudder.campaigns

import better.files.File
import com.normation.errors.IOResult
import zio.ZIO


trait CampaignRepository {
def getAll(): IOResult[List[Campaign]]
def get(id : CampaignId) : IOResult[Campaign]
def save(c : Campaign): IOResult[Campaign]
}


class CampaignRepositoryImpl(campaignSerializer: CampaignSerializer, path : File) extends CampaignRepository {

def getAll(): IOResult[List[Campaign]] = {
for {
jsonFiles <- IOResult.effect{path.collectChildren(_.extension.exists(_ =="json"))}
campaigns <- ZIO.foreach(jsonFiles.toList) {
json => campaignSerializer.parse(json.contentAsString)
}
} yield {
campaigns
}
}
def get(id : CampaignId) : IOResult[Campaign] = {
for {
content <- IOResult.effect (s"error when getting campaign file for campain with id '${id.value}'"){
val file = path / (s"${id.value}.json")
file.createFileIfNotExists(createParents = true)
file
}
campaign <- campaignSerializer.parse(content.contentAsString)
} yield {
campaign
}
}
def save(c : Campaign): IOResult[Campaign] = {
for {
file <- IOResult.effect (s"error when creating campaign file for campain with id '${c.info.id.value}'"){
val file = path / (s"${c.info.id.value}.json")

file.createFileIfNotExists(true)
file
}
_ <- CampaignLogger.info(file.pathAsString)
content <- campaignSerializer.serialize(c)
_ <- IOResult.effect{
file.write(content)
}
} yield {
c
}
}
}
Loading

0 comments on commit d855f09

Please sign in to comment.