Используйте rego для сравнения значений до и после из списка входов

Когда я запускаю следующее, я могу сравнить значения параметра instance_class и подсчитать количество расхождений:

modifies_instance_class[resource_type] = num {
    some resource_type
    resource_types[resource_type]
    all := resources[resource_type]
    modifies := [res |  res:= all[_]; res.change.after.instance_class != res.change.before.instance_class]
    num := count(modifies)
}

Однако я хотел бы иметь возможность использовать один и тот же блок кода для сравнения различных значений параметров, содержащихся в моем списке my_params. Я пробовал следующее, но это не работает.

my_params = {"instance_class", "engine_version", "identifier"}

modifies_instance_class[resource_type] = num {
    some resource_type
    some parameter
    resource_types[resource_type]
    my_params[parameter]
    all := resources[resource_type]
    modifies := [res |  res:= all[_]; res.change.after.parameter != res.change.before.parameter]
    num := count(modifies)
}

2 ответа

Как сказал Патрик Ист, есть пара проблем. Я подумал, что разобью каждую из них и предоставлю немного больше деталей. Вот последняя версия, которую я придумал: https://play.openpolicyagent.org/p/rl1p43N5HR

1-я проблема: найдите поле с помощью parameter вар

Выражение res.change.after.parameter ищет поле с именем "parameter" в объекте, на который ссылается res.change.after. Другими словами, синтаксисfoo.bar сахар для foo["bar"]. Выражениеfoo["bar"] выбирает поле "bar" (строка 'bar') от значения foo. Чтобы исправить это, изменитеres.change.after.parameter к res.change.after[parameter]. Сейчасparameter относится к переменной.

modifies_instance_class_V1[resource_type] = num {
    some resource_type, parameter
    resource_types[resource_type]
    my_params[parameter]
    all := resources[resource_type]
    modifies := [res | res:= all[_]; res.change.after[parameter] != res.change.before[parameter]]
    num := count(modifies)
}

2-я проблема: сделать modifies_instance_class генерировать одно значение для каждого ключа

Вторая проблема заключается в том, что правило будет генерировать конфликтующие значения для modifies_instance_classдокумент. Правила формыp[x] = y { ... } создать документ JSON с именем p где x а также yключи и значения (соответственно). OPA должен обрабатывать конфликтующие значения одного и того же ключа как ошибки времени выполнения.

Например, представьте, что OPA содержит следующие данные:

resource_types = {"servers"}

resources = {
  "servers": [{
      "after": {"instance_class": "ic1", "identifier": "id1"},
      "before": {"instance_class": "ic2", "identifier": "id1"}
  }]
}

Данные показывают, что есть один resource_type ("servers") и один сервер. Данные для сервера указывают наinstance_class поле изменено.

Когда OPA оценивает правило, он будет искать назначения переменных, которые удовлетворяют всем выражениям в теле правила:

# This is the rule body from above.
some resource_type, parameter
resource_types[resource_type]
my_params[parameter]
all := resources[resource_type]
modifies := [res | res:= all[_]; res.change.after[parameter] != res.change.before[parameter]]
num := count(modifies)

В этом случае OPA находит значения для resource_type, parameter, all, modifies, а также num. Игнорироватьall, modifies, а также _ переменные пока.

В этом случае OPA найдет два набора присвоений переменных:

{resource_type: "servers", parameter: "instance_class", num: 1}
{resource_type: "servers", parameter: "identifier", num: 0}

Проблема в том, что правило генерирует отображение из resource_type к num. В этом случае это производит{"servers": 1} а также {"servers": 0}. Это конфликт. Какой документ правильный?

Чтобы решить эту проблему, мы можем просто переместить my_params[parameter] выражение в теле понимания массива.

modifies_instance_class_V2[resource_type] = num {
    some resource_type
    resource_types[resource_type]
    all := resources[resource_type]
    modifies := [[res, parameter] |   # NOTE: this generates an array of resource/parameter tuples now.
        some parameter
        my_params[parameter]
        res := all[_]
        res.change.after[parameter] != res.change.before[parameter]
    ]
    num := count(modifies)
}

С этим изменением OPA найдет только один набор назначений переменных:

{resource_type: "servers", num: 1}

Обратите внимание: если OPA найдет другие значения для resource_type это было бы хорошо, потому что это будут разные ключи в документе, созданном modifies_instance_class.

3-я проблема: что делать, если поле параметра отсутствует до или после?

Есть еще одна проблема. В приведенных выше примерах мы предполагали, что поиск поля, на которое указываетparametervar всегда будет возвращать значение. Что делать, если это не так? Что делать, если у одного из объектов "до" или "после" отсутствует поле?

В этом случае ссылка res.change.after[parameter] или res.change.before[parameter]будет неопределенным. Если какое-либо значение не определено, выражениеres.change.after[parameter] != res.change.after[parameter]также не определено ( https://www.openpolicyagent.org/docs/latest/). Если выражение не определено, OPA не может утверждать, что оно истинно, поэтому результат не включается в результат (или, в данном случае, в массив, вычисленный интерпретацией).

В зависимости от характера данных это может иметь (а может и не иметь) значение. Чтобы справиться с этим, мы можем расширить проверку, чтобы иметь дело со случаем, когда поле не определено с одной стороны (или с обеих).

modifies_instance_class_V3[resource_type] = num {
    some resource_type
    resource_types[resource_type]
    all := resources[resource_type]
    modifies := [[res, parameter] |
        some parameter
        my_params[parameter]
        res := all[_]
        not same_or_both_undefined(res.change.after, res.change.before, parameter)
    ]
    num := count(modifies)
}

same_or_both_undefined(a, b, k) = true {
    a[k] == b[k]
}

same_or_both_undefined(a, b, k) = true {
    not a[k]
    not b[k]
}

Примечание: в этом случае мы должны использовать вспомогательную функцию, потому что хотим выразить логическое ИЛИ: https://www.openpolicyagent.org/docs/latest/.

Пара вещей:

Первый выпуск использует res.change.after.parameterтаким образом вызовет проблемы. Это использование ключа с именем "параметр", а не переменной. Вам нужно будет сделать что-то вродеres.change.after[parameter]. Это должно избежать первой ошибки.. Но выявляет следующую (и более серьезную) проблему:

eval_conflict_error: object keys must be unique

Проблема в том, как вы их объединяете, где у вас есть my_params[parameter] и считая modifies. Это даст вамnum отличные от parameter для каждого resource_type (т.е. некоторый тип ресурса x может иметь количество 0 за instance_class и 2 за identifier), что сложно из-за того, как вы структурируете результаты как карту resource_type к num (поскольку для каждого потенциально может быть>1 числа)

Если вас интересует только счетчик, вы можете сделать еще одно понимание, которое проверяет каждый параметр для каждого ресурса (возможно, не лучший вариант... но работает). Пример этой работы находится здесь: https://play.openpolicyagent.org/p/5T5TntBygd

modifies_instance_class[resource_type] = num {
    some resource_type
    resource_types[resource_type]
    all := resources[resource_type]
    modifies := [res |
        res := all[_]       # For each resource
        # Check if one of the params we care about changed
        changed_params := [p | p := my_params[_]; res.change.after[p] != res.change.before[p]]  
        count(changed_params) > 0
    ]
    num := count(modifies)
}

Обратите внимание, что этот пример не очень хорош для предоставления какой-либо обратной связи о том, что изменилось, поэтому в качестве помощника он может быть полезен, но его оценка может варьироваться.

Другие вопросы по тегам