@@ -368,7 +376,8 @@ const Step = (props: {step: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1
(props.step.setCanaryScale && openCanary) ||
(props.step.analysis && openAnalysis) ||
(props.step.setHeaderRoute && openHeader) ||
- (props.step.setMirrorRoute && openMirror)
+ (props.step.setMirrorRoute && openMirror) ||
+ (props.step.plugin && openPlugin)
? 'steps__step-title--experiment'
: ''
}`}
@@ -395,6 +404,11 @@ const Step = (props: {step: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1
)}
+ {props.step.plugin && props.step.plugin.config && (
+ setOpenPlugin(!openPlugin)}>
+
+
+ )}
{props.step.experiment?.templates && (
@@ -419,6 +433,7 @@ const Step = (props: {step: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1
)}
{props.step?.setCanaryScale && openCanary && }
+ {props.step?.plugin && openPlugin && }
{props.step?.setHeaderRoute && openHeader && }
{props.step?.setMirrorRoute && openMirror && }
@@ -563,3 +578,13 @@ const WidgetItemSetHeader = ({values}: {values: GithubComArgoprojArgoRolloutsPkg
);
};
+
+const WidgetItemPlugin = ({values}: {values: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PluginStep}) => {
+ if (!values.config) return null;
+ return (
+
+
CONFIG
+
{YAML.stringify(values.config)}
+
+ );
+};
diff --git a/ui/src/models/rollout/generated/.swagger-codegen/VERSION b/ui/src/models/rollout/generated/.swagger-codegen/VERSION
index 9b77657dc7..248908e600 100644
--- a/ui/src/models/rollout/generated/.swagger-codegen/VERSION
+++ b/ui/src/models/rollout/generated/.swagger-codegen/VERSION
@@ -1 +1 @@
-3.0.25
\ No newline at end of file
+3.0.54
\ No newline at end of file
diff --git a/ui/src/models/rollout/generated/api.ts b/ui/src/models/rollout/generated/api.ts
index 114f705aee..642bd611ed 100755
--- a/ui/src/models/rollout/generated/api.ts
+++ b/ui/src/models/rollout/generated/api.ts
@@ -13,7 +13,7 @@
*/
import * as url from "url";
-import * as portableFetch from "portable-fetch";
+import * as isomorphicFetch from "isomorphic-fetch";
import { Configuration } from "./configuration";
const BASE_PATH = "/".replace(/\/+$/, "");
@@ -56,13 +56,13 @@ export interface FetchArgs {
export class BaseAPI {
protected configuration: Configuration;
- constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected fetch: FetchAPI = portableFetch) {
+ constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected fetch: FetchAPI = isomorphicFetch) {
if (configuration) {
this.configuration = configuration;
this.basePath = configuration.basePath || this.basePath;
}
}
-};
+}
/**
*
@@ -71,7 +71,7 @@ export class BaseAPI {
* @extends {Error}
*/
export class RequiredError extends Error {
- name: "RequiredError"
+ name = "RequiredError"
constructor(public field: string, msg?: string) {
super(msg);
}
@@ -711,6 +711,12 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStatu
* @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStatus
*/
stablePingPong?: string;
+ /**
+ *
+ * @type {Array}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStatus
+ */
+ stepPluginStatuses?: Array;
}
/**
* CanaryStep defines a step of a canary deployment.
@@ -760,6 +766,12 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStep
* @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStep
*/
setMirrorRoute?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetMirrorRoute;
+ /**
+ *
+ * @type {GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PluginStep}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStep
+ */
+ plugin?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PluginStep;
}
/**
*
@@ -1043,6 +1055,12 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1DatadogMetr
* @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1DatadogMetric
*/
aggregator?: string;
+ /**
+ *
+ * @type {GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SecretRef}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1DatadogMetric
+ */
+ secretRef?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SecretRef;
}
/**
* DryRun defines the settings for running the analysis in Dry-Run mode.
@@ -1473,6 +1491,12 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1Metric {
* @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1Metric
*/
provider?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1MetricProvider;
+ /**
+ *
+ * @type {K8sIoApimachineryPkgUtilIntstrIntOrString}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1Metric
+ */
+ consecutiveSuccessLimit?: K8sIoApimachineryPkgUtilIntstrIntOrString;
}
/**
*
@@ -1631,6 +1655,12 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1MetricResul
* @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1MetricResult
*/
metadata?: { [key: string]: string; };
+ /**
+ *
+ * @type {number}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1MetricResult
+ */
+ consecutiveSuccess?: number;
}
/**
*
@@ -1650,6 +1680,12 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1NewRelicMet
* @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1NewRelicMetric
*/
query?: string;
+ /**
+ *
+ * @type {string}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1NewRelicMetric
+ */
+ timeout?: string;
}
/**
*
@@ -1681,6 +1717,12 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1NginxTraffi
* @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1NginxTrafficRouting
*/
stableIngresses?: Array;
+ /**
+ *
+ * @type {{ [key: string]: string; }}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1NginxTrafficRouting
+ */
+ canaryIngressAnnotations?: { [key: string]: string; };
}
/**
*
@@ -1782,6 +1824,25 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PingPongSpe
*/
pongService?: string;
}
+/**
+ *
+ * @export
+ * @interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PluginStep
+ */
+export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PluginStep {
+ /**
+ *
+ * @type {string}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PluginStep
+ */
+ name?: string;
+ /**
+ *
+ * @type {string}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PluginStep
+ */
+ config?: string;
+}
/**
*
* @export
@@ -1856,6 +1917,37 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PrometheusM
* @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PrometheusMetric
*/
headers?: Array;
+ /**
+ *
+ * @type {GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PrometheusRangeQueryArgs}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PrometheusMetric
+ */
+ rangeQuery?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PrometheusRangeQueryArgs;
+}
+/**
+ *
+ * @export
+ * @interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PrometheusRangeQueryArgs
+ */
+export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PrometheusRangeQueryArgs {
+ /**
+ *
+ * @type {string}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PrometheusRangeQueryArgs
+ */
+ start?: string;
+ /**
+ *
+ * @type {string}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PrometheusRangeQueryArgs
+ */
+ end?: string;
+ /**
+ * The maximum time between two slices from the start to end (e.g. 30s, 5m, 1h).
+ * @type {string}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PrometheusRangeQueryArgs
+ */
+ step?: string;
}
/**
*
@@ -2639,6 +2731,25 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SecretKeyRe
*/
key?: string;
}
+/**
+ *
+ * @export
+ * @interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SecretRef
+ */
+export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SecretRef {
+ /**
+ * Name refers to the name of the secret that should be used to integrate with Datadog.
+ * @type {string}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SecretRef
+ */
+ name?: string;
+ /**
+ *
+ * @type {boolean}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SecretRef
+ */
+ namespaced?: boolean;
+}
/**
*
* @export
@@ -2758,6 +2869,85 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SkyWalkingM
*/
interval?: string;
}
+/**
+ *
+ * @export
+ * @interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus
+ */
+export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus {
+ /**
+ *
+ * @type {number}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus
+ */
+ index?: number;
+ /**
+ *
+ * @type {string}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus
+ */
+ name?: string;
+ /**
+ *
+ * @type {string}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus
+ */
+ operation?: string;
+ /**
+ *
+ * @type {string}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus
+ */
+ phase?: string;
+ /**
+ *
+ * @type {string}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus
+ */
+ message?: string;
+ /**
+ *
+ * @type {K8sIoApimachineryPkgApisMetaV1Time}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus
+ */
+ startedAt?: K8sIoApimachineryPkgApisMetaV1Time;
+ /**
+ *
+ * @type {K8sIoApimachineryPkgApisMetaV1Time}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus
+ */
+ updatedAt?: K8sIoApimachineryPkgApisMetaV1Time;
+ /**
+ *
+ * @type {K8sIoApimachineryPkgApisMetaV1Time}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus
+ */
+ finishedAt?: K8sIoApimachineryPkgApisMetaV1Time;
+ /**
+ *
+ * @type {string}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus
+ */
+ backoff?: string;
+ /**
+ *
+ * @type {number}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus
+ */
+ executions?: number;
+ /**
+ *
+ * @type {boolean}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus
+ */
+ disabled?: boolean;
+ /**
+ *
+ * @type {string}
+ * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus
+ */
+ status?: string;
+}
/**
*
* @export
@@ -7883,7 +8073,7 @@ export const RolloutServiceApiFetchParamCreator = function (configuration?: Conf
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
- delete localVarUrlObj.search;
+ localVarUrlObj.search = null;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
const needsSerialization = ("RolloutAbortRolloutRequest" !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
localVarRequestOptions.body = needsSerialization ? JSON.stringify(body || {}) : (body || "");
@@ -7907,7 +8097,7 @@ export const RolloutServiceApiFetchParamCreator = function (configuration?: Conf
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
- delete localVarUrlObj.search;
+ localVarUrlObj.search = null;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
return {
@@ -7941,7 +8131,7 @@ export const RolloutServiceApiFetchParamCreator = function (configuration?: Conf
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
- delete localVarUrlObj.search;
+ localVarUrlObj.search = null;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
return {
@@ -7969,7 +8159,7 @@ export const RolloutServiceApiFetchParamCreator = function (configuration?: Conf
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
- delete localVarUrlObj.search;
+ localVarUrlObj.search = null;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
return {
@@ -8010,7 +8200,7 @@ export const RolloutServiceApiFetchParamCreator = function (configuration?: Conf
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
- delete localVarUrlObj.search;
+ localVarUrlObj.search = null;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
const needsSerialization = ("RolloutPromoteRolloutRequest" !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
localVarRequestOptions.body = needsSerialization ? JSON.stringify(body || {}) : (body || "");
@@ -8053,7 +8243,7 @@ export const RolloutServiceApiFetchParamCreator = function (configuration?: Conf
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
- delete localVarUrlObj.search;
+ localVarUrlObj.search = null;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
const needsSerialization = ("RolloutRestartRolloutRequest" !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
localVarRequestOptions.body = needsSerialization ? JSON.stringify(body || {}) : (body || "");
@@ -8096,7 +8286,7 @@ export const RolloutServiceApiFetchParamCreator = function (configuration?: Conf
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
- delete localVarUrlObj.search;
+ localVarUrlObj.search = null;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
const needsSerialization = ("RolloutRetryRolloutRequest" !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
localVarRequestOptions.body = needsSerialization ? JSON.stringify(body || {}) : (body || "");
@@ -8157,7 +8347,7 @@ export const RolloutServiceApiFetchParamCreator = function (configuration?: Conf
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
- delete localVarUrlObj.search;
+ localVarUrlObj.search = null;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
const needsSerialization = ("RolloutSetImageRequest" !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
localVarRequestOptions.body = needsSerialization ? JSON.stringify(body || {}) : (body || "");
@@ -8206,7 +8396,7 @@ export const RolloutServiceApiFetchParamCreator = function (configuration?: Conf
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
- delete localVarUrlObj.search;
+ localVarUrlObj.search = null;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
const needsSerialization = ("RolloutUndoRolloutRequest" !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
localVarRequestOptions.body = needsSerialization ? JSON.stringify(body || {}) : (body || "");
@@ -8230,7 +8420,7 @@ export const RolloutServiceApiFetchParamCreator = function (configuration?: Conf
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
- delete localVarUrlObj.search;
+ localVarUrlObj.search = null;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
return {
@@ -8264,7 +8454,7 @@ export const RolloutServiceApiFetchParamCreator = function (configuration?: Conf
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
- delete localVarUrlObj.search;
+ localVarUrlObj.search = null;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
return {
@@ -8292,7 +8482,7 @@ export const RolloutServiceApiFetchParamCreator = function (configuration?: Conf
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
- delete localVarUrlObj.search;
+ localVarUrlObj.search = null;
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
return {
@@ -8319,7 +8509,7 @@ export const RolloutServiceApiFp = function(configuration?: Configuration) {
*/
rolloutServiceAbortRollout(body: RolloutAbortRolloutRequest, namespace: string, name: string, options?: any): (fetch?: FetchAPI, basePath?: string) => Promise {
const localVarFetchArgs = RolloutServiceApiFetchParamCreator(configuration).rolloutServiceAbortRollout(body, namespace, name, options);
- return (fetch: FetchAPI = portableFetch, basePath: string = BASE_PATH) => {
+ return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => {
return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => {
if (response.status >= 200 && response.status < 300) {
return response.json();
@@ -8336,7 +8526,7 @@ export const RolloutServiceApiFp = function(configuration?: Configuration) {
*/
rolloutServiceGetNamespace(options?: any): (fetch?: FetchAPI, basePath?: string) => Promise {
const localVarFetchArgs = RolloutServiceApiFetchParamCreator(configuration).rolloutServiceGetNamespace(options);
- return (fetch: FetchAPI = portableFetch, basePath: string = BASE_PATH) => {
+ return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => {
return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => {
if (response.status >= 200 && response.status < 300) {
return response.json();
@@ -8355,7 +8545,7 @@ export const RolloutServiceApiFp = function(configuration?: Configuration) {
*/
rolloutServiceGetRolloutInfo(namespace: string, name: string, options?: any): (fetch?: FetchAPI, basePath?: string) => Promise {
const localVarFetchArgs = RolloutServiceApiFetchParamCreator(configuration).rolloutServiceGetRolloutInfo(namespace, name, options);
- return (fetch: FetchAPI = portableFetch, basePath: string = BASE_PATH) => {
+ return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => {
return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => {
if (response.status >= 200 && response.status < 300) {
return response.json();
@@ -8373,7 +8563,7 @@ export const RolloutServiceApiFp = function(configuration?: Configuration) {
*/
rolloutServiceListRolloutInfos(namespace: string, options?: any): (fetch?: FetchAPI, basePath?: string) => Promise {
const localVarFetchArgs = RolloutServiceApiFetchParamCreator(configuration).rolloutServiceListRolloutInfos(namespace, options);
- return (fetch: FetchAPI = portableFetch, basePath: string = BASE_PATH) => {
+ return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => {
return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => {
if (response.status >= 200 && response.status < 300) {
return response.json();
@@ -8393,7 +8583,7 @@ export const RolloutServiceApiFp = function(configuration?: Configuration) {
*/
rolloutServicePromoteRollout(body: RolloutPromoteRolloutRequest, namespace: string, name: string, options?: any): (fetch?: FetchAPI, basePath?: string) => Promise {
const localVarFetchArgs = RolloutServiceApiFetchParamCreator(configuration).rolloutServicePromoteRollout(body, namespace, name, options);
- return (fetch: FetchAPI = portableFetch, basePath: string = BASE_PATH) => {
+ return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => {
return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => {
if (response.status >= 200 && response.status < 300) {
return response.json();
@@ -8413,7 +8603,7 @@ export const RolloutServiceApiFp = function(configuration?: Configuration) {
*/
rolloutServiceRestartRollout(body: RolloutRestartRolloutRequest, namespace: string, name: string, options?: any): (fetch?: FetchAPI, basePath?: string) => Promise {
const localVarFetchArgs = RolloutServiceApiFetchParamCreator(configuration).rolloutServiceRestartRollout(body, namespace, name, options);
- return (fetch: FetchAPI = portableFetch, basePath: string = BASE_PATH) => {
+ return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => {
return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => {
if (response.status >= 200 && response.status < 300) {
return response.json();
@@ -8433,7 +8623,7 @@ export const RolloutServiceApiFp = function(configuration?: Configuration) {
*/
rolloutServiceRetryRollout(body: RolloutRetryRolloutRequest, namespace: string, name: string, options?: any): (fetch?: FetchAPI, basePath?: string) => Promise {
const localVarFetchArgs = RolloutServiceApiFetchParamCreator(configuration).rolloutServiceRetryRollout(body, namespace, name, options);
- return (fetch: FetchAPI = portableFetch, basePath: string = BASE_PATH) => {
+ return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => {
return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => {
if (response.status >= 200 && response.status < 300) {
return response.json();
@@ -8456,7 +8646,7 @@ export const RolloutServiceApiFp = function(configuration?: Configuration) {
*/
rolloutServiceSetRolloutImage(body: RolloutSetImageRequest, namespace: string, rollout: string, container: string, image: string, tag: string, options?: any): (fetch?: FetchAPI, basePath?: string) => Promise {
const localVarFetchArgs = RolloutServiceApiFetchParamCreator(configuration).rolloutServiceSetRolloutImage(body, namespace, rollout, container, image, tag, options);
- return (fetch: FetchAPI = portableFetch, basePath: string = BASE_PATH) => {
+ return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => {
return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => {
if (response.status >= 200 && response.status < 300) {
return response.json();
@@ -8477,7 +8667,7 @@ export const RolloutServiceApiFp = function(configuration?: Configuration) {
*/
rolloutServiceUndoRollout(body: RolloutUndoRolloutRequest, namespace: string, rollout: string, revision: string, options?: any): (fetch?: FetchAPI, basePath?: string) => Promise {
const localVarFetchArgs = RolloutServiceApiFetchParamCreator(configuration).rolloutServiceUndoRollout(body, namespace, rollout, revision, options);
- return (fetch: FetchAPI = portableFetch, basePath: string = BASE_PATH) => {
+ return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => {
return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => {
if (response.status >= 200 && response.status < 300) {
return response.json();
@@ -8494,7 +8684,7 @@ export const RolloutServiceApiFp = function(configuration?: Configuration) {
*/
rolloutServiceVersion(options?: any): (fetch?: FetchAPI, basePath?: string) => Promise {
const localVarFetchArgs = RolloutServiceApiFetchParamCreator(configuration).rolloutServiceVersion(options);
- return (fetch: FetchAPI = portableFetch, basePath: string = BASE_PATH) => {
+ return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => {
return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => {
if (response.status >= 200 && response.status < 300) {
return response.json();
@@ -8513,7 +8703,7 @@ export const RolloutServiceApiFp = function(configuration?: Configuration) {
*/
rolloutServiceWatchRolloutInfo(namespace: string, name: string, options?: any): (fetch?: FetchAPI, basePath?: string) => Promise {
const localVarFetchArgs = RolloutServiceApiFetchParamCreator(configuration).rolloutServiceWatchRolloutInfo(namespace, name, options);
- return (fetch: FetchAPI = portableFetch, basePath: string = BASE_PATH) => {
+ return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => {
return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => {
if (response.status >= 200 && response.status < 300) {
return response.json();
@@ -8531,7 +8721,7 @@ export const RolloutServiceApiFp = function(configuration?: Configuration) {
*/
rolloutServiceWatchRolloutInfos(namespace: string, options?: any): (fetch?: FetchAPI, basePath?: string) => Promise {
const localVarFetchArgs = RolloutServiceApiFetchParamCreator(configuration).rolloutServiceWatchRolloutInfos(namespace, options);
- return (fetch: FetchAPI = portableFetch, basePath: string = BASE_PATH) => {
+ return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => {
return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => {
if (response.status >= 200 && response.status < 300) {
return response.json();
diff --git a/ui/src/models/rollout/generated/custom.d.ts b/ui/src/models/rollout/generated/custom.d.ts
index 9a5ceb3588..5d917c1d30 100644
--- a/ui/src/models/rollout/generated/custom.d.ts
+++ b/ui/src/models/rollout/generated/custom.d.ts
@@ -1,2 +1,2 @@
-declare module 'portable-fetch';
+declare module 'isomorphic-fetch';
declare module 'url';
\ No newline at end of file
diff --git a/ui/yarn.lock b/ui/yarn.lock
index c8fbf6b82b..68dcaf2cf7 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -8891,33 +8891,10 @@ load-json-file@^2.0.0:
pify "^2.0.0"
strip-bom "^3.0.0"
-loader-runner@^2.4.0:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
- integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
-
-loader-utils@1.2.3:
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
- integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
- dependencies:
- big.js "^5.2.2"
- emojis-list "^2.0.0"
- json5 "^1.0.1"
-
-loader-utils@2.0.0, loader-utils@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
- integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
- dependencies:
- big.js "^5.2.2"
- emojis-list "^3.0.0"
- json5 "^2.1.2"
-
-loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
- integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
+loader-utils@^2.0.0, loader-utils@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
+ integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
diff --git a/utils/analysis/factory.go b/utils/analysis/factory.go
index 0e17e587a7..c2fefd3068 100644
--- a/utils/analysis/factory.go
+++ b/utils/analysis/factory.go
@@ -157,15 +157,48 @@ func ValidateMetric(metric v1alpha1.Metric) error {
failureLimit = metric.FailureLimit.IntValue()
}
+ consecutiveSuccessLimit := 0
+ if metric.ConsecutiveSuccessLimit != nil {
+ consecutiveSuccessLimit = metric.ConsecutiveSuccessLimit.IntValue()
+ }
+
inconclusiveLimit := 0
if metric.InconclusiveLimit != nil {
inconclusiveLimit = metric.InconclusiveLimit.IntValue()
}
+ // Negative value checks
+ if consecutiveSuccessLimit < 0 {
+ return fmt.Errorf("consecutiveSuccessLimit must be >= 0")
+ }
+ if failureLimit < -1 {
+ return fmt.Errorf("failureLimit must be >= 0, or -1 to be disabled")
+ }
+ // if consecutiveSuccessLimit is disabled (set to 0), AND
+ // failureLimit is disabled (set to -1), then this is invalid
+ if consecutiveSuccessLimit == 0 && failureLimit == -1 {
+ return fmt.Errorf("failureLimit and consecutiveSuccessLimit cannot both be disabled")
+ }
+
+ if inconclusiveLimit < 0 {
+ return fmt.Errorf("inconclusiveLimit must be >= 0")
+ }
+
+ if metric.ConsecutiveErrorLimit != nil && metric.ConsecutiveErrorLimit.IntValue() < 0 {
+ return fmt.Errorf("consecutiveErrorLimit must be >= 0")
+ }
+
if count > 0 {
- if count < failureLimit {
- return fmt.Errorf("count must be >= failureLimit")
+ if consecutiveSuccessLimit >= 0 && failureLimit >= 0 {
+ if count < failureLimit+consecutiveSuccessLimit {
+ return fmt.Errorf("count (%d) must be >= failureLimit + consecutiveSuccessLimit (%d + %d) if both >= 0", count, failureLimit, consecutiveSuccessLimit)
+ }
+ } else if consecutiveSuccessLimit >= 0 && failureLimit < 0 {
+ if count < consecutiveSuccessLimit {
+ return fmt.Errorf("count must be >= consecutiveSuccessLimit")
+ }
}
+
if count < inconclusiveLimit {
return fmt.Errorf("count must be >= inconclusiveLimit")
}
@@ -184,16 +217,6 @@ func ValidateMetric(metric v1alpha1.Metric) error {
}
}
- if failureLimit < 0 {
- return fmt.Errorf("failureLimit must be >= 0")
- }
- if inconclusiveLimit < 0 {
- return fmt.Errorf("inconclusiveLimit must be >= 0")
- }
-
- if metric.ConsecutiveErrorLimit != nil && metric.ConsecutiveErrorLimit.IntValue() < 0 {
- return fmt.Errorf("consecutiveErrorLimit must be >= 0")
- }
numProviders := 0
if metric.Provider.Prometheus != nil {
numProviders++
diff --git a/utils/analysis/factory_test.go b/utils/analysis/factory_test.go
index d3fe7dfec1..8873ce0f3f 100644
--- a/utils/analysis/factory_test.go
+++ b/utils/analysis/factory_test.go
@@ -4,8 +4,6 @@ import (
"fmt"
"testing"
- "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
- "github.com/argoproj/argo-rollouts/utils/annotations"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
@@ -13,6 +11,9 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/utils/pointer"
+
+ "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
+ "github.com/argoproj/argo-rollouts/utils/annotations"
)
func TestBuildArgumentsForRolloutAnalysisRun(t *testing.T) {
@@ -178,7 +179,7 @@ func TestBackgroundLabels(t *testing.T) {
}
func TestValidateMetrics(t *testing.T) {
- t.Run("Ensure count >= failureLimit", func(t *testing.T) {
+ t.Run("Ensure count >= failureLimit + consecutiveSuccessLimit", func(t *testing.T) {
failureLimit := intstr.FromInt(2)
count := intstr.FromInt(1)
spec := v1alpha1.AnalysisTemplateSpec{
@@ -194,7 +195,7 @@ func TestValidateMetrics(t *testing.T) {
},
}
err := ValidateMetrics(spec.Metrics)
- assert.EqualError(t, err, "metrics[0]: count must be >= failureLimit")
+ assert.EqualError(t, err, "metrics[0]: count (1) must be >= failureLimit + consecutiveSuccessLimit (2 + 0) if both >= 0")
count = intstr.FromInt(0)
spec.Metrics[0].Count = &count
err = ValidateMetrics(spec.Metrics)
@@ -323,8 +324,8 @@ func TestValidateMetrics(t *testing.T) {
err := ValidateMetrics(spec.Metrics)
assert.EqualError(t, err, "metrics[1]: duplicate name 'success-rate'")
})
- t.Run("Ensure failureLimit >= 0", func(t *testing.T) {
- failureLimit := intstr.FromInt(-1)
+ t.Run("Ensure failureLimit >= -1", func(t *testing.T) {
+ failureLimit := intstr.FromInt(-2)
spec := v1alpha1.AnalysisTemplateSpec{
Metrics: []v1alpha1.Metric{
{
@@ -337,7 +338,7 @@ func TestValidateMetrics(t *testing.T) {
},
}
err := ValidateMetrics(spec.Metrics)
- assert.EqualError(t, err, "metrics[0]: failureLimit must be >= 0")
+ assert.EqualError(t, err, "metrics[0]: failureLimit must be >= 0, or -1 to be disabled")
})
t.Run("Ensure inconclusiveLimit >= 0", func(t *testing.T) {
inconclusiveLimit := intstr.FromInt(-1)
@@ -407,6 +408,40 @@ func TestValidateMetrics(t *testing.T) {
err := ValidateMetrics(spec.Metrics)
assert.EqualError(t, err, "metrics[0]: multiple providers specified")
})
+ t.Run("Ensure consecutiveSuccessLimit >= 0", func(t *testing.T) {
+ consecutiveSuccessLimit := intstr.FromInt(-1)
+ spec := v1alpha1.AnalysisTemplateSpec{
+ Metrics: []v1alpha1.Metric{
+ {
+ Name: "success-rate",
+ ConsecutiveSuccessLimit: &consecutiveSuccessLimit,
+ Provider: v1alpha1.MetricProvider{
+ Prometheus: &v1alpha1.PrometheusMetric{},
+ },
+ },
+ },
+ }
+ err := ValidateMetrics(spec.Metrics)
+ assert.EqualError(t, err, "metrics[0]: consecutiveSuccessLimit must be >= 0")
+ })
+ t.Run("Ensure consecutiveSuccessLimit and failureLimit are not both disabled", func(t *testing.T) {
+ consecutiveSuccessLimit := intstr.FromInt(0)
+ failureLimit := intstr.FromInt(-1)
+ spec := v1alpha1.AnalysisTemplateSpec{
+ Metrics: []v1alpha1.Metric{
+ {
+ Name: "success-rate",
+ ConsecutiveSuccessLimit: &consecutiveSuccessLimit,
+ FailureLimit: &failureLimit,
+ Provider: v1alpha1.MetricProvider{
+ Prometheus: &v1alpha1.PrometheusMetric{},
+ },
+ },
+ },
+ }
+ err := ValidateMetrics(spec.Metrics)
+ assert.EqualError(t, err, "metrics[0]: failureLimit and consecutiveSuccessLimit cannot both be disabled")
+ })
}
// TestResolveMetricArgs verifies that metric arguments are resolved
@@ -444,7 +479,7 @@ func TestResolveMetricArgsWithQuotes(t *testing.T) {
}
newMetric, err := ResolveMetricArgs(metric, arguments)
assert.NoError(t, err)
- assert.Equal(t, fmt.Sprintf(arg), newMetric.SuccessCondition)
+ assert.Equal(t, arg, newMetric.SuccessCondition)
}
func Test_extractValueFromRollout(t *testing.T) {
diff --git a/utils/analysis/helpers.go b/utils/analysis/helpers.go
index 42ded67315..142b6daeb1 100644
--- a/utils/analysis/helpers.go
+++ b/utils/analysis/helpers.go
@@ -7,7 +7,6 @@ import (
"fmt"
"regexp"
- "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
log "github.com/sirupsen/logrus"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -15,6 +14,8 @@ import (
"k8s.io/apimachinery/pkg/runtime"
patchtypes "k8s.io/apimachinery/pkg/types"
+ "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
+
argoprojclient "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned/typed/rollouts/v1alpha1"
)
diff --git a/utils/annotations/annotations.go b/utils/annotations/annotations.go
index 121c6c0803..067492bc2b 100644
--- a/utils/annotations/annotations.go
+++ b/utils/annotations/annotations.go
@@ -28,6 +28,8 @@ const (
DesiredReplicasAnnotation = RolloutLabel + "/desired-replicas"
// WorkloadGenerationAnnotation is the generation of the referenced workload
WorkloadGenerationAnnotation = RolloutLabel + "/workload-generation"
+ // NotificationEngineAnnotation the annotation notification engine uses to determine if it should notify
+ NotificationEngineAnnotation = "notified.notifications.argoproj.io"
)
// GetDesiredReplicasAnnotation returns the number of desired replicas
@@ -219,6 +221,7 @@ var annotationsToSkip = map[string]bool{
RevisionAnnotation: true,
RevisionHistoryAnnotation: true,
DesiredReplicasAnnotation: true,
+ NotificationEngineAnnotation: true,
}
// skipCopyAnnotation returns true if we should skip copying the annotation with the given annotation key
diff --git a/utils/apisix/apisix.go b/utils/apisix/apisix.go
index 3f5984e022..010aaf60ba 100644
--- a/utils/apisix/apisix.go
+++ b/utils/apisix/apisix.go
@@ -4,10 +4,11 @@ import (
"context"
"strings"
- "github.com/argoproj/argo-rollouts/utils/defaults"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
+
+ "github.com/argoproj/argo-rollouts/utils/defaults"
)
const apisixRoutes = "apisixroutes"
diff --git a/utils/appmesh/appmesh.go b/utils/appmesh/appmesh.go
index 83e77572ff..19c9d2133b 100644
--- a/utils/appmesh/appmesh.go
+++ b/utils/appmesh/appmesh.go
@@ -3,10 +3,11 @@ package appmesh
import (
"context"
- "github.com/argoproj/argo-rollouts/utils/defaults"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
+
+ "github.com/argoproj/argo-rollouts/utils/defaults"
)
const AppMeshCRDGroup = "appmesh.k8s.aws"
diff --git a/utils/aws/aws.go b/utils/aws/aws.go
index 42b4907836..42685302fd 100644
--- a/utils/aws/aws.go
+++ b/utils/aws/aws.go
@@ -8,7 +8,6 @@ import (
"github.com/aws/aws-sdk-go-v2/aws"
- "github.com/argoproj/argo-rollouts/utils/defaults"
"github.com/aws/aws-sdk-go-v2/config"
elbv2 "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
@@ -18,6 +17,8 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/dynamic"
+
+ "github.com/argoproj/argo-rollouts/utils/defaults"
)
// AWSLoadBalancerV2TagKeyResourceID is the tag applied to an AWS resource by the AWS Load Balancer
diff --git a/utils/aws/mocks/ELBv2APIClient.go b/utils/aws/mocks/ELBv2APIClient.go
index 5e9f9dc380..f004df4702 100644
--- a/utils/aws/mocks/ELBv2APIClient.go
+++ b/utils/aws/mocks/ELBv2APIClient.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.32.0. DO NOT EDIT.
+// Code generated by mockery v2.42.2. DO NOT EDIT.
package mocks
@@ -25,6 +25,10 @@ func (_m *ELBv2APIClient) DescribeListeners(_a0 context.Context, _a1 *elasticloa
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
+ if len(ret) == 0 {
+ panic("no return value specified for DescribeListeners")
+ }
+
var r0 *elasticloadbalancingv2.DescribeListenersOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *elasticloadbalancingv2.DescribeListenersInput, ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeListenersOutput, error)); ok {
@@ -58,6 +62,10 @@ func (_m *ELBv2APIClient) DescribeLoadBalancers(_a0 context.Context, _a1 *elasti
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
+ if len(ret) == 0 {
+ panic("no return value specified for DescribeLoadBalancers")
+ }
+
var r0 *elasticloadbalancingv2.DescribeLoadBalancersOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *elasticloadbalancingv2.DescribeLoadBalancersInput, ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeLoadBalancersOutput, error)); ok {
@@ -91,6 +99,10 @@ func (_m *ELBv2APIClient) DescribeRules(ctx context.Context, params *elasticload
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
+ if len(ret) == 0 {
+ panic("no return value specified for DescribeRules")
+ }
+
var r0 *elasticloadbalancingv2.DescribeRulesOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *elasticloadbalancingv2.DescribeRulesInput, ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeRulesOutput, error)); ok {
@@ -124,6 +136,10 @@ func (_m *ELBv2APIClient) DescribeTags(ctx context.Context, params *elasticloadb
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
+ if len(ret) == 0 {
+ panic("no return value specified for DescribeTags")
+ }
+
var r0 *elasticloadbalancingv2.DescribeTagsOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *elasticloadbalancingv2.DescribeTagsInput, ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeTagsOutput, error)); ok {
@@ -157,6 +173,10 @@ func (_m *ELBv2APIClient) DescribeTargetGroups(_a0 context.Context, _a1 *elastic
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
+ if len(ret) == 0 {
+ panic("no return value specified for DescribeTargetGroups")
+ }
+
var r0 *elasticloadbalancingv2.DescribeTargetGroupsOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *elasticloadbalancingv2.DescribeTargetGroupsInput, ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeTargetGroupsOutput, error)); ok {
@@ -190,6 +210,10 @@ func (_m *ELBv2APIClient) DescribeTargetHealth(ctx context.Context, params *elas
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
+ if len(ret) == 0 {
+ panic("no return value specified for DescribeTargetHealth")
+ }
+
var r0 *elasticloadbalancingv2.DescribeTargetHealthOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *elasticloadbalancingv2.DescribeTargetHealthInput, ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeTargetHealthOutput, error)); ok {
diff --git a/utils/conditions/conditions.go b/utils/conditions/conditions.go
index 77cd401c63..57d8c0f02f 100644
--- a/utils/conditions/conditions.go
+++ b/utils/conditions/conditions.go
@@ -133,6 +133,11 @@ const (
// RolloutExperimentFailedMessage is added in a rollout when the experiment owned by a rollout fails to show any progress
RolloutExperimentFailedMessage = "Experiment '%s' owned by the Rollout '%q' has timed out."
+ // RolloutReconciliationErrorReason is added in a rollout when the reconciliation returns an error preventing progress
+ RolloutReconciliationErrorReason = "ReconciliationError"
+ // RolloutReconciliationErrorMessage is added in a rollout when the reconciliation returns an error preventing progress
+ RolloutReconciliationErrorMessage = "Reconciliation failed with error: %v"
+
// TimedOutReason is added in a rollout when its newest replica set fails to show any progress
// within the given deadline (progressDeadlineSeconds).
TimedOutReason = "ProgressDeadlineExceeded"
@@ -157,6 +162,12 @@ const (
// ServiceReferencingManagedService is added in a rollout when the multiple rollouts reference a Rollout
ServiceReferencingManagedService = "Service %q is managed by another Rollout"
+ // StepPluginTransitionReason is added to a Rollout when a step plugin transition to an unsuccessful phase
+ StepPluginTransitionReason = "StepPluginTransition"
+ StepPluginTransitionRunMessage = "Step plugin %s (step %d) transitioned to %s"
+ StepPluginTransitionAbortMessage = "Step plugin %s (step %d) aborted (%s)"
+ StepPluginTransitionTerminateMessage = "Step plugin %s (step %d) terminated (%s)"
+
// TargetGroupHealthyReason is emitted when target group has been verified
TargetGroupVerifiedReason = "TargetGroupVerified"
TargetGroupVerifiedRegistrationMessage = "Service %s (TargetGroup %s) verified: %d endpoints registered"
@@ -302,7 +313,7 @@ func RolloutHealthy(rollout *v1alpha1.Rollout, newStatus *v1alpha1.RolloutStatus
}
// RolloutCompleted considers a rollout to be complete once StableRS == CurrentPodHash
-func RolloutCompleted(rollout *v1alpha1.Rollout, newStatus *v1alpha1.RolloutStatus) bool {
+func RolloutCompleted(newStatus *v1alpha1.RolloutStatus) bool {
return newStatus.StableRS != "" && newStatus.StableRS == newStatus.CurrentPodHash
}
@@ -335,7 +346,7 @@ func RolloutTimedOut(rollout *v1alpha1.Rollout, newStatus *v1alpha1.RolloutStatu
// When a rollout is retried, the controller should not evaluate for a timeout based on the
// aborted condition because the abort could have happened a while back and the rollout should
// not enter degraded as a result of that
- if condition == nil || condition.Reason == RolloutAbortedReason {
+ if condition == nil || condition.Reason == RolloutAbortedReason || condition.Reason == RolloutPausedReason {
return false
}
diff --git a/utils/conditions/rollouts_test.go b/utils/conditions/rollouts_test.go
index 842f01f594..af6a69d40c 100644
--- a/utils/conditions/rollouts_test.go
+++ b/utils/conditions/rollouts_test.go
@@ -499,10 +499,10 @@ func TestRolloutComplete(t *testing.T) {
return r
}
r := rollout(5, 5, 5, 5)
- assert.Equal(t, true, RolloutCompleted(r, &r.Status))
+ assert.Equal(t, true, RolloutCompleted(&r.Status))
r.Status.StableRS = "not-current-pod-hash"
- assert.Equal(t, false, RolloutCompleted(r, &r.Status))
+ assert.Equal(t, false, RolloutCompleted(&r.Status))
}
func TestRolloutTimedOut(t *testing.T) {
@@ -555,6 +555,22 @@ func TestRolloutTimedOut(t *testing.T) {
},
expected: true,
},
+ {
+ name: "Rollout has not time out when paused",
+ progressDeadlineSeconds: 5,
+ newStatus: v1alpha1.RolloutStatus{
+ Conditions: conditions(RolloutPausedReason, before),
+ },
+ expected: false,
+ },
+ {
+ name: "Rollout has not time out when aborted",
+ progressDeadlineSeconds: 5,
+ newStatus: v1alpha1.RolloutStatus{
+ Conditions: conditions(RolloutAbortedReason, before),
+ },
+ expected: false,
+ },
}
for i := range tests {
test := tests[i]
diff --git a/utils/config/config.go b/utils/config/config.go
index 7989374e8e..7b20520341 100644
--- a/utils/config/config.go
+++ b/utils/config/config.go
@@ -4,26 +4,29 @@ import (
"context"
"fmt"
"regexp"
+ "slices"
"sync"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
- "github.com/argoproj/argo-rollouts/utils/defaults"
- "github.com/argoproj/argo-rollouts/utils/plugin/types"
v1 "k8s.io/api/core/v1"
k8errors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/yaml"
+
+ "github.com/argoproj/argo-rollouts/utils/defaults"
+ "github.com/argoproj/argo-rollouts/utils/plugin/types"
)
// Config is the in memory representation of the configmap with some additional fields/functions for ease of use.
type Config struct {
configMap *v1.ConfigMap
plugins []types.PluginItem
+ lock *sync.RWMutex
}
var configMemoryCache *Config
-var mutex sync.RWMutex
+var mutex = &sync.RWMutex{}
// Regex to match plugin names, this matches github username and repo limits
var re = regexp.MustCompile(`^([a-zA-Z0-9\-]+)\/{1}([a-zA-Z0-9_\-.]+)$`)
@@ -34,7 +37,9 @@ func InitializeConfig(k8sClientset kubernetes.Interface, configMapName string) (
configMapCluster, err := k8sClientset.CoreV1().ConfigMaps(defaults.Namespace()).Get(context.Background(), configMapName, metav1.GetOptions{})
if err != nil {
if k8errors.IsNotFound(err) {
- configMemoryCache = &Config{} // We create an empty config so that we don't try to initialize again
+ configMemoryCache = &Config{
+ lock: &sync.RWMutex{},
+ } // We create an empty config so that we don't try to initialize again
// If the configmap is not found, we return
return configMemoryCache, nil
}
@@ -45,16 +50,31 @@ func InitializeConfig(k8sClientset kubernetes.Interface, configMapName string) (
if err = yaml.Unmarshal([]byte(configMapCluster.Data["trafficRouterPlugins"]), &trafficRouterPlugins); err != nil {
return nil, fmt.Errorf("failed to unmarshal traffic router plugins while initializing: %w", err)
}
+ for i := range trafficRouterPlugins {
+ trafficRouterPlugins[i].Type = types.PluginTypeTrafficRouter
+ }
var metricProviderPlugins []types.PluginItem
if err = yaml.Unmarshal([]byte(configMapCluster.Data["metricProviderPlugins"]), &metricProviderPlugins); err != nil {
return nil, fmt.Errorf("failed to unmarshal metric provider plugins while initializing: %w", err)
}
+ for i := range metricProviderPlugins {
+ metricProviderPlugins[i].Type = types.PluginTypeMetricProvider
+ }
+
+ var stepPlugins []types.PluginItem
+ if err = yaml.Unmarshal([]byte(configMapCluster.Data["stepPlugins"]), &stepPlugins); err != nil {
+ return nil, fmt.Errorf("failed to unmarshal step plugins while initializing: %w", err)
+ }
+ for i := range stepPlugins {
+ stepPlugins[i].Type = types.PluginTypeStep
+ }
mutex.Lock()
configMemoryCache = &Config{
configMap: configMapCluster,
- plugins: append(trafficRouterPlugins, metricProviderPlugins...),
+ plugins: slices.Concat(trafficRouterPlugins, metricProviderPlugins, stepPlugins),
+ lock: &sync.RWMutex{},
}
mutex.Unlock()
@@ -70,6 +90,7 @@ func InitializeConfig(k8sClientset kubernetes.Interface, configMapName string) (
func GetConfig() (*Config, error) {
mutex.RLock()
defer mutex.RUnlock()
+
if configMemoryCache == nil {
return nil, fmt.Errorf("config not initialized, please initialize before use")
}
@@ -85,12 +106,30 @@ func UnInitializeConfig() {
// GetAllPlugins returns a flattened list of plugin items. This is useful for iterating over all plugins.
func (c *Config) GetAllPlugins() []types.PluginItem {
- mutex.RLock()
- defer mutex.RUnlock()
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ // Return a copy of the slice
+ return append([]types.PluginItem{}, c.plugins...)
+}
+
+// GetPlugin returns the plugin item by name and type if it exists
+func (c *Config) GetPlugin(name string, pluginType types.PluginType) *types.PluginItem {
+ for _, plugin := range c.GetAllPlugins() {
+ if plugin.Name == name && plugin.Type == pluginType {
+ return &plugin
+ }
+ }
+ return nil
+}
- var copiedPlugins []types.PluginItem
- copiedPlugins = append(copiedPlugins, configMemoryCache.plugins...)
- return copiedPlugins
+func (c *Config) ValidateConfig() error {
+ for _, pluginItem := range c.GetAllPlugins() {
+ matches := re.FindAllStringSubmatch(pluginItem.Name, -1)
+ if len(matches) != 1 || len(matches[0]) != 3 {
+ return fmt.Errorf("plugin repository (%s) must be in the format of /", pluginItem.Name)
+ }
+ }
+ return nil
}
// GetPluginDirectoryAndFilename this functions return the directory and file name from a given pluginName such as
@@ -105,16 +144,3 @@ func GetPluginDirectoryAndFilename(pluginName string) (directory string, filenam
return namespace, plugin, nil
}
-
-func (c *Config) ValidateConfig() error {
- mutex.RLock()
- defer mutex.RUnlock()
-
- for _, pluginItem := range c.GetAllPlugins() {
- matches := re.FindAllStringSubmatch(pluginItem.Name, -1)
- if len(matches) != 1 || len(matches[0]) != 3 {
- return fmt.Errorf("plugin repository (%s) must be in the format of /", pluginItem.Name)
- }
- }
- return nil
-}
diff --git a/utils/controller/controller.go b/utils/controller/controller.go
index 2530e1d5fa..b5c1fc875b 100644
--- a/utils/controller/controller.go
+++ b/utils/controller/controller.go
@@ -6,6 +6,8 @@ import (
"runtime/debug"
"time"
+ "k8s.io/apimachinery/pkg/api/errors"
+
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -157,6 +159,11 @@ func processNextWorkItem(ctx context.Context, workqueue workqueue.RateLimitingIn
if err := runSyncHandler(); err != nil {
logCtx.Errorf("%s syncHandler error: %v", objType, err)
metricsServer.IncError(namespace, name, objType)
+
+ if errors.IsNotFound(err) {
+ workqueue.Forget(obj)
+ return nil
+ }
// Put the item back on
// the workqueue to handle any transient errors.
workqueue.AddRateLimited(key)
diff --git a/utils/controller/controller_test.go b/utils/controller/controller_test.go
index 3761d05ba5..39f3c134b5 100644
--- a/utils/controller/controller_test.go
+++ b/utils/controller/controller_test.go
@@ -23,13 +23,14 @@ import (
kubetesting "k8s.io/client-go/testing"
"k8s.io/client-go/util/workqueue"
+ "k8s.io/client-go/tools/cache"
+
"github.com/argoproj/argo-rollouts/controller/metrics"
register "github.com/argoproj/argo-rollouts/pkg/apis/rollouts"
"github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
"github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned/fake"
informers "github.com/argoproj/argo-rollouts/pkg/client/informers/externalversions"
"github.com/argoproj/argo-rollouts/utils/log"
- "k8s.io/client-go/tools/cache"
)
func TestProcessNextWorkItemHandlePanic(t *testing.T) {
diff --git a/utils/evaluate/evaluate.go b/utils/evaluate/evaluate.go
index a2c9a607cd..435622303e 100644
--- a/utils/evaluate/evaluate.go
+++ b/utils/evaluate/evaluate.go
@@ -6,6 +6,7 @@ import (
"math"
"reflect"
"strconv"
+ "time"
"github.com/antonmedv/expr"
"github.com/antonmedv/expr/file"
@@ -56,6 +57,39 @@ func EvaluateResult(result any, metric v1alpha1.Metric, logCtx logrus.Entry) (v1
return v1alpha1.AnalysisPhaseSuccessful, nil
}
+func EvalTime(expression string) (time.Time, error) {
+ var err error
+
+ env := map[string]any{
+ "isNaN": math.IsNaN,
+ "isInf": isInf,
+ }
+
+ unwrapFileErr := func(e error) error {
+ if fileErr, ok := err.(*file.Error); ok {
+ e = errors.New(fileErr.Message)
+ }
+ return e
+ }
+
+ program, err := expr.Compile(expression, expr.Env(env))
+ if err != nil {
+ return time.Time{}, unwrapFileErr(err)
+ }
+
+ output, err := expr.Run(program, env)
+ if err != nil {
+ return time.Time{}, unwrapFileErr(err)
+ }
+
+ switch val := output.(type) {
+ case time.Time:
+ return val, nil
+ default:
+ return time.Time{}, fmt.Errorf("expected time.Time, but got %T", val)
+ }
+}
+
// EvalCondition evaluates the condition with the resultValue as an input
func EvalCondition(resultValue any, condition string) (bool, error) {
var err error
diff --git a/utils/evaluate/evaluate_test.go b/utils/evaluate/evaluate_test.go
index 7839e47256..61dfc702b0 100644
--- a/utils/evaluate/evaluate_test.go
+++ b/utils/evaluate/evaluate_test.go
@@ -5,6 +5,7 @@ import (
"math"
"reflect"
"testing"
+ "time"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
@@ -329,3 +330,21 @@ func TestValueFromPointer(t *testing.T) {
assert.True(t, valueFromPointer(1) == 1)
assert.True(t, valueFromPointer(false) == false)
}
+
+func TestEvalTimeWithSuccessExpr(t *testing.T) {
+ status, err := EvalTime(`date("2023-08-14 00:00:00", "2006-01-02 15:04:05", "UTC") - duration("1h")`)
+ assert.Equal(t, time.Date(2023, time.August, 13, 23, 0, 0, 0, time.UTC), status)
+ assert.NoError(t, err)
+}
+
+func TestEvalTimeWithNotTimeResult(t *testing.T) {
+ status, err := EvalTime(`hello`)
+ assert.Equal(t, time.Time{}, status)
+ assert.Error(t, err)
+}
+
+func TestEvalTimeWithInvalidExpression(t *testing.T) {
+ status, err := EvalTime(`now() -- ?`)
+ assert.Equal(t, time.Time{}, status)
+ assert.Error(t, err)
+}
diff --git a/utils/istio/istio_test.go b/utils/istio/istio_test.go
index a4e5f5fe46..8e0d20d6d7 100644
--- a/utils/istio/istio_test.go
+++ b/utils/istio/istio_test.go
@@ -6,11 +6,12 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
- "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
- "github.com/argoproj/argo-rollouts/utils/defaults"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/runtime"
dynamicfake "k8s.io/client-go/dynamic/fake"
+
+ "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
+ "github.com/argoproj/argo-rollouts/utils/defaults"
)
func NewFakeDynamicClient(objects ...runtime.Object) *dynamicfake.FakeDynamicClient {
diff --git a/utils/plugin/downloader.go b/utils/plugin/downloader.go
index b8b1ad6263..17c7ba1bf7 100644
--- a/utils/plugin/downloader.go
+++ b/utils/plugin/downloader.go
@@ -1,6 +1,7 @@
package plugin
import (
+ "context"
"crypto/sha256"
"fmt"
"io"
@@ -10,6 +11,9 @@ import (
"path/filepath"
"time"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+
argoConfig "github.com/argoproj/argo-rollouts/utils/config"
"github.com/argoproj/argo-rollouts/utils/defaults"
@@ -19,15 +23,20 @@ import (
// FileDownloader is an interface that allows us to mock the http.Get function
type FileDownloader interface {
- Get(url string) (resp *http.Response, err error)
+ Get(url string, header http.Header) (resp *http.Response, err error)
}
// FileDownloaderImpl is the default/real implementation of the FileDownloader interface
type FileDownloaderImpl struct {
}
-func (fd FileDownloaderImpl) Get(url string) (resp *http.Response, err error) {
- return http.Get(url)
+func (fd FileDownloaderImpl) Get(url string, header http.Header) (resp *http.Response, err error) {
+ request, err := http.NewRequest(http.MethodGet, url, nil)
+ if err != nil {
+ return nil, err
+ }
+ request.Header = header
+ return http.DefaultClient.Do(request)
}
// checkPluginExists this function checks if the plugin exists in the configured path on the filesystem
@@ -43,12 +52,17 @@ func checkPluginExists(pluginLocation string) error {
}
func checkShaOfPlugin(pluginLocation string, expectedSha256 string) (bool, error) {
- hasher := sha256.New()
fileBytes, err := os.ReadFile(pluginLocation)
if err != nil {
return false, fmt.Errorf("failed to read file %s: %w", pluginLocation, err)
}
- fileSha256 := fmt.Sprintf("%x", hasher.Sum(fileBytes))
+ var fileSha256 string
+ if len(expectedSha256) == 64 {
+ fileSha256 = fmt.Sprintf("%x", sha256.Sum256(fileBytes))
+ } else {
+ hasher := sha256.New()
+ fileSha256 = fmt.Sprintf("%x", hasher.Sum(fileBytes))
+ }
match := fileSha256 == expectedSha256
if !match {
log.Printf("expected sha256: %s, actual sha256: %s, of downloaded metric plugin (%s)", expectedSha256, fileSha256, pluginLocation)
@@ -56,12 +70,17 @@ func checkShaOfPlugin(pluginLocation string, expectedSha256 string) (bool, error
return match, nil
}
-func downloadFile(filepath string, url string, downloader FileDownloader) error {
- // Get the data
- resp, err := downloader.Get(url)
+func downloadFile(filepath string, url string, downloader FileDownloader, header http.Header) error {
+ // Get the data with credentials
+ resp, err := downloader.Get(url, header)
if err != nil {
return fmt.Errorf("failed to download file from %s: %w", url, err)
}
+
+ if isFailure(resp.StatusCode) {
+ return fmt.Errorf("failed to download file from %s: response code %s", url, http.StatusText(resp.StatusCode))
+ }
+
defer resp.Body.Close()
// Create the file
@@ -87,7 +106,7 @@ func downloadFile(filepath string, url string, downloader FileDownloader) error
}
// DownloadPlugins this function downloads and/or checks that a plugin executable exits on the filesystem
-func DownloadPlugins(fd FileDownloader) error {
+func DownloadPlugins(fd FileDownloader, kubeClient kubernetes.Interface) error {
config, err := argoConfig.GetConfig()
if err != nil {
return fmt.Errorf("failed to get config: %w", err)
@@ -121,7 +140,18 @@ func DownloadPlugins(fd FileDownloader) error {
case "http", "https":
log.Infof("Downloading plugin %s from: %s", plugin.Name, plugin.Location)
startTime := time.Now()
- err = downloadFile(finalFileLocation, urlObj.String(), fd)
+ requestHeader := http.Header{}
+ for _, header := range plugin.HeadersFrom {
+ secret, err := kubeClient.CoreV1().Secrets(defaults.Namespace()).Get(context.Background(), header.SecretRef.Name, metav1.GetOptions{})
+ if err != nil {
+ return fmt.Errorf("failed to get secret in secretRef: %w", err)
+ }
+ for k, v := range secret.Data {
+ requestHeader.Add(k, string(v))
+ }
+ }
+
+ err = downloadFile(finalFileLocation, urlObj.String(), fd, requestHeader)
if err != nil {
return fmt.Errorf("failed to download plugin from %s: %w", plugin.Location, err)
}
@@ -196,3 +226,8 @@ func copyFile(src, dst string) error {
}
return nil
}
+
+// isFailure determines if the response has a 2xx response
+func isFailure(statusCode int) bool {
+ return statusCode < http.StatusOK || statusCode >= http.StatusBadRequest
+}
diff --git a/utils/plugin/downloader_test.go b/utils/plugin/downloader_test.go
index 75dc4cae71..601e20dda4 100644
--- a/utils/plugin/downloader_test.go
+++ b/utils/plugin/downloader_test.go
@@ -10,18 +10,30 @@ import (
"github.com/argoproj/argo-rollouts/utils/config"
- "github.com/argoproj/argo-rollouts/utils/defaults"
"github.com/tj/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
+
+ "github.com/argoproj/argo-rollouts/utils/defaults"
)
type MockFileDownloader struct {
FileDownloader
}
-func (m MockFileDownloader) Get(url string) (*http.Response, error) {
+func (m MockFileDownloader) Get(url string, header http.Header) (*http.Response, error) {
+ if url == "https://test/plugin/fail" {
+ return &http.Response{
+ Status: "404",
+ StatusCode: 404,
+ Proto: "HTTP/1.1",
+ ProtoMajor: 1,
+ ProtoMinor: 1,
+ Header: nil,
+ ContentLength: 4,
+ }, nil
+ }
responseBody := io.NopCloser(bytes.NewReader([]byte(`test`)))
return &http.Response{
Status: "200",
@@ -47,16 +59,23 @@ func TestPlugin(t *testing.T) {
Name: "argo-rollouts-config",
Namespace: "argo-rollouts",
},
- Data: map[string]string{"metricProviderPlugins": "\n - name: argoproj-labs/http\n location: https://test/plugin\n - name: argoproj-labs/http-sha\n location: https://test/plugin\n sha256: 74657374e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},
+ Data: map[string]string{"metricProviderPlugins": "\n - name: argoproj-labs/http\n location: https://test/plugin\n - name: argoproj-labs/http-sha\n location: https://test/plugin\n sha256: 74657374e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\n - name: argoproj-labs/http-sha-correct\n location: https://test/plugin\n sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n - name: argoproj-labs/http-headers-correct\n location: https://test/plugin\n headersFrom:\n - secretRef:\n name: secret-name"},
}
- client := fake.NewSimpleClientset(cm)
+ secret := &v1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "secret-name",
+ Namespace: "argo-rollouts",
+ },
+ Data: map[string][]byte{"Authorization": []byte("Basic VE9LRU4=")},
+ }
+ client := fake.NewSimpleClientset(cm, secret)
config.UnInitializeConfig()
_, err := config.InitializeConfig(client, "argo-rollouts-config")
assert.NoError(t, err)
- err = DownloadPlugins(MockFileDownloader{})
+ err = DownloadPlugins(MockFileDownloader{}, client)
assert.NoError(t, err)
dir, filename, err := config.GetPluginDirectoryAndFilename("argoproj-labs/http")
@@ -68,12 +87,60 @@ func TestPlugin(t *testing.T) {
dir, filename, err = config.GetPluginDirectoryAndFilename("argoproj-labs/http-sha")
assert.NoError(t, err)
+ err = os.Remove(filepath.Join(defaults.DefaultRolloutPluginFolder, dir, filename))
+ assert.NoError(t, err)
+
+ dir, filename, err = config.GetPluginDirectoryAndFilename("argoproj-labs/http-headers-correct")
+ assert.NoError(t, err)
+
err = os.Remove(filepath.Join(defaults.DefaultRolloutPluginFolder, dir, filename))
assert.NoError(t, err)
err = os.RemoveAll(defaults.DefaultRolloutPluginFolder)
assert.NoError(t, err)
})
+ t.Run("test failed download", func(t *testing.T) {
+ cm := &v1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "argo-rollouts-config",
+ Namespace: "argo-rollouts",
+ },
+ Data: map[string]string{"metricProviderPlugins": "\n - name: argoproj-labs/http-fail\n location: https://test/plugin/fail\n"},
+ }
+ client := fake.NewSimpleClientset(cm)
+
+ config.UnInitializeConfig()
+
+ _, err := config.InitializeConfig(client, "argo-rollouts-config")
+ assert.NoError(t, err)
+
+ err = DownloadPlugins(MockFileDownloader{}, client)
+ assert.Error(t, err)
+ err = os.RemoveAll(defaults.DefaultRolloutPluginFolder)
+ assert.NoError(t, err)
+ })
+
+ t.Run("test failed finding secret for header", func(t *testing.T) {
+ cm := &v1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "argo-rollouts-config",
+ Namespace: "argo-rollouts",
+ },
+ Data: map[string]string{"metricProviderPlugins": " - name: argoproj-labs/http-headers-correct\n location: https://test/plugin\n headersFrom:\n - secretRef:\n name: secret-name"},
+ }
+ client := fake.NewSimpleClientset(cm)
+
+ config.UnInitializeConfig()
+
+ _, err := config.InitializeConfig(client, "argo-rollouts-config")
+ assert.NoError(t, err)
+
+ err = DownloadPlugins(MockFileDownloader{}, client)
+ assert.Error(t, err)
+ err = os.RemoveAll(defaults.DefaultRolloutPluginFolder)
+ assert.NoError(t, err)
+ })
+
t.Run("test bad sha", func(t *testing.T) {
cm := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
@@ -89,7 +156,7 @@ func TestPlugin(t *testing.T) {
_, err := config.InitializeConfig(client, defaults.DefaultRolloutsConfigMapName)
assert.NoError(t, err)
- err = DownloadPlugins(MockFileDownloader{})
+ err = DownloadPlugins(MockFileDownloader{}, client)
assert.Error(t, err)
dir, filename, err := config.GetPluginDirectoryAndFilename("argoproj-labs/http-badsha")
@@ -107,9 +174,9 @@ func TestPlugin(t *testing.T) {
cm, err := config.InitializeConfig(client, defaults.DefaultRolloutsConfigMapName)
assert.NoError(t, err)
- assert.Equal(t, cm, &config.Config{})
+ assert.Equal(t, 0, len(cm.GetAllPlugins()))
- err = DownloadPlugins(MockFileDownloader{})
+ err = DownloadPlugins(MockFileDownloader{}, client)
assert.NoError(t, err)
err = os.RemoveAll(defaults.DefaultRolloutPluginFolder)
assert.NoError(t, err)
@@ -130,7 +197,7 @@ func TestPlugin(t *testing.T) {
_, err := config.InitializeConfig(client, defaults.DefaultRolloutsConfigMapName)
assert.NoError(t, err)
- err = DownloadPlugins(MockFileDownloader{})
+ err = DownloadPlugins(MockFileDownloader{}, client)
assert.NoError(t, err)
dir, filename, err := config.GetPluginDirectoryAndFilename("argoproj-labs/file-plugin")
@@ -159,7 +226,7 @@ func TestPlugin(t *testing.T) {
_, err = config.InitializeConfig(client, defaults.DefaultRolloutsConfigMapName)
assert.NoError(t, err)
- err = DownloadPlugins(MockFileDownloader{})
+ err = DownloadPlugins(MockFileDownloader{}, client)
assert.NoError(t, err)
dir, filename, err := config.GetPluginDirectoryAndFilename("namespace/file-plugin")
@@ -186,7 +253,7 @@ func TestPlugin(t *testing.T) {
_, err := config.InitializeConfig(client, "argo-rollouts-config")
assert.Error(t, err)
- err = DownloadPlugins(MockFileDownloader{})
+ err = DownloadPlugins(MockFileDownloader{}, client)
assert.Error(t, err)
})
@@ -205,7 +272,7 @@ func TestPlugin(t *testing.T) {
_, err := config.InitializeConfig(client, "argo-rollouts-config")
assert.NoError(t, err)
- err = DownloadPlugins(MockFileDownloader{})
+ err = DownloadPlugins(MockFileDownloader{}, client)
assert.Error(t, err)
err = os.RemoveAll(defaults.DefaultRolloutPluginFolder)
@@ -251,9 +318,18 @@ func TestCheckShaOfPlugin(t *testing.T) {
}
func TestDownloadFile(t *testing.T) {
- err := downloadFile("error", "", FileDownloaderImpl{})
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "failed to download file from")
+ t.Run("test sha of real file", func(t *testing.T) {
+ err := downloadFile("error", " ", FileDownloaderImpl{}, nil)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "failed to download file from")
+ })
+
+ t.Run("test download fail with invalid url", func(t *testing.T) {
+ url := "://example.com"
+ err := downloadFile("error", url, FileDownloaderImpl{}, nil)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "failed to download file from")
+ })
}
func Test_copyFile(t *testing.T) {
diff --git a/utils/plugin/plugin.go b/utils/plugin/plugin.go
index 3fd7a013a0..a76ed5e942 100644
--- a/utils/plugin/plugin.go
+++ b/utils/plugin/plugin.go
@@ -5,30 +5,32 @@ import (
"path/filepath"
"github.com/argoproj/argo-rollouts/utils/defaults"
+ "github.com/argoproj/argo-rollouts/utils/plugin/types"
"github.com/argoproj/argo-rollouts/utils/config"
)
// GetPluginInfo returns the location & command arguments of the plugin on the filesystem via plugin name. If the plugin is not
// configured in the configmap, an error is returned.
-func GetPluginInfo(pluginName string) (string, []string, error) {
+func GetPluginInfo(pluginName string, pluginType types.PluginType) (string, []string, error) {
configMap, err := config.GetConfig()
if err != nil {
return "", nil, fmt.Errorf("failed to get config: %w", err)
}
- for _, item := range configMap.GetAllPlugins() {
- if pluginName == item.Name {
- dir, filename, err := config.GetPluginDirectoryAndFilename(item.Name)
- if err != nil {
- return "", nil, err
- }
- absFilePath, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, dir, filename))
- if err != nil {
- return "", nil, fmt.Errorf("failed to get absolute path of plugin folder: %w", err)
- }
- return absFilePath, item.Args, nil
- }
+ plugin := configMap.GetPlugin(pluginName, pluginType)
+ if plugin == nil {
+ return "", nil, fmt.Errorf("plugin %s not configured in configmap", pluginName)
}
- return "", nil, fmt.Errorf("plugin %s not configured in configmap", pluginName)
+
+ dir, filename, err := config.GetPluginDirectoryAndFilename(plugin.Name)
+ if err != nil {
+ return "", nil, err
+ }
+ absFilePath, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, dir, filename))
+ if err != nil {
+ return "", nil, fmt.Errorf("failed to get absolute path of plugin folder: %w", err)
+ }
+ return absFilePath, plugin.Args, nil
+
}
diff --git a/utils/plugin/plugin_test.go b/utils/plugin/plugin_test.go
index 7226d2d312..f380105a5b 100644
--- a/utils/plugin/plugin_test.go
+++ b/utils/plugin/plugin_test.go
@@ -5,12 +5,14 @@ import (
"testing"
"github.com/argoproj/argo-rollouts/utils/defaults"
+ "github.com/argoproj/argo-rollouts/utils/plugin/types"
- "github.com/argoproj/argo-rollouts/utils/config"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
+
+ "github.com/argoproj/argo-rollouts/utils/config"
)
func TestGetPluginInfo(t *testing.T) {
@@ -29,14 +31,14 @@ func TestGetPluginInfo(t *testing.T) {
_, err := config.InitializeConfig(client, "argo-rollouts-config")
assert.NoError(t, err)
- location, args, err := GetPluginInfo("argoproj-labs/http")
+ location, args, err := GetPluginInfo("argoproj-labs/http", types.PluginTypeMetricProvider)
assert.NoError(t, err)
fp, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, "argoproj-labs/http"))
assert.NoError(t, err)
assert.Equal(t, fp, location)
assert.Equal(t, args, cmdArgs)
- _, args, _ = GetPluginInfo("argoproj-labs/http-sha")
+ _, args, _ = GetPluginInfo("argoproj-labs/http-sha", types.PluginTypeMetricProvider)
assert.Equal(t, len(args), 0)
})
@@ -54,14 +56,39 @@ func TestGetPluginInfo(t *testing.T) {
_, err := config.InitializeConfig(client, "argo-rollouts-config")
assert.NoError(t, err)
- location, args, err := GetPluginInfo("argoproj-labs/router")
+ location, args, err := GetPluginInfo("argoproj-labs/router", types.PluginTypeTrafficRouter)
assert.NoError(t, err)
fp, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, "argoproj-labs/router"))
assert.NoError(t, err)
assert.Equal(t, fp, location)
assert.Equal(t, args, cmdArgs)
- _, args, _ = GetPluginInfo("argoproj-labs/router-sha")
+ _, args, _ = GetPluginInfo("argoproj-labs/router-sha", types.PluginTypeTrafficRouter)
+ assert.Equal(t, len(args), 0)
+ })
+
+ t.Run("tests getting plugin location of step plugins", func(t *testing.T) {
+
+ cm := &v1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "argo-rollouts-config",
+ Namespace: "argo-rollouts",
+ },
+ Data: map[string]string{"stepPlugins": "\n - name: argoproj-labs/steps\n location: https://test/plugin\n args: [\"-l 2\"]\n - name: argoproj-labs/steps-sha\n location: https://test/plugin\n sha256: 74657374e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},
+ }
+ client := fake.NewSimpleClientset(cm)
+
+ _, err := config.InitializeConfig(client, "argo-rollouts-config")
+ assert.NoError(t, err)
+
+ location, args, err := GetPluginInfo("argoproj-labs/steps", types.PluginTypeStep)
+ assert.NoError(t, err)
+ fp, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, "argoproj-labs/steps"))
+ assert.NoError(t, err)
+ assert.Equal(t, fp, location)
+ assert.Equal(t, args, cmdArgs)
+
+ _, args, _ = GetPluginInfo("argoproj-labs/step-sha", types.PluginTypeStep)
assert.Equal(t, len(args), 0)
})
@@ -78,10 +105,30 @@ func TestGetPluginInfo(t *testing.T) {
_, err := config.InitializeConfig(client, "argo-rollouts-config")
assert.NoError(t, err)
- location, args, err := GetPluginInfo("does-not-exist")
+ location, args, err := GetPluginInfo("does-not-exist", types.PluginTypeMetricProvider)
assert.Error(t, err)
assert.Equal(t, "plugin does-not-exist not configured in configmap", err.Error())
assert.Equal(t, "", location)
assert.Equal(t, len(args), 0)
})
+
+ t.Run("test getting plugin location from a plugin of a different type", func(t *testing.T) {
+ cm := &v1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "argo-rollouts-config",
+ Namespace: "argo-rollouts",
+ },
+ Data: map[string]string{"metricProviderPlugins": "\n - name: argoproj-labs/http\n location: https://test/plugin\n - name: argoproj-labs/http-sha\n location: https://test/plugin\n sha256: 74657374e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},
+ }
+ client := fake.NewSimpleClientset(cm)
+
+ _, err := config.InitializeConfig(client, "argo-rollouts-config")
+ assert.NoError(t, err)
+
+ location, args, err := GetPluginInfo("argoproj-labs/http", types.PluginTypeStep)
+ assert.Error(t, err)
+ assert.Equal(t, "plugin argoproj-labs/http not configured in configmap", err.Error())
+ assert.Equal(t, "", location)
+ assert.Equal(t, len(args), 0)
+ })
}
diff --git a/utils/plugin/types/steps.go b/utils/plugin/types/steps.go
new file mode 100644
index 0000000000..f54d7ec00b
--- /dev/null
+++ b/utils/plugin/types/steps.go
@@ -0,0 +1,55 @@
+package types
+
+import (
+ "encoding/json"
+ "fmt"
+ "time"
+)
+
+// StepPhase is the type of phase of step plugin result
+type StepPhase string
+
+const (
+ // PhaseRunning is the Running phase of a step plugin
+ PhaseRunning StepPhase = "Running"
+ // PhaseRunning is the Successful phase of a step plugin
+ PhaseSuccessful StepPhase = "Successful"
+ // PhaseRunning is the Failed phase of a step plugin
+ PhaseFailed StepPhase = "Failed"
+ // PhaseRunning is the Error phase of a step plugin
+ PhaseError StepPhase = "Error"
+)
+
+// RpcStepContext is the context of the step plugin operation
+type RpcStepContext struct {
+ // PluginName is the name of the plugin as defined by the user
+ PluginName string
+ // Config holds the user specified configuration in the Rollout object for this plugin step
+ Config json.RawMessage
+ // Status holds a previous execution status related to the operation
+ Status json.RawMessage
+}
+
+type RpcStepResult struct {
+ // Phase of the operation to idicate if it has completed or not
+ Phase StepPhase
+ // Message contains information about the execution
+ Message string
+ // RequeueAfter is the duration to wait before executing the operation again when it does not return a completed phase
+ RequeueAfter time.Duration
+ // Status hold the execution status of this plugin step. It can be used to persist a state between executions
+ Status json.RawMessage
+}
+
+// Validate the phase of a step plugin
+func (p StepPhase) Validate() error {
+ switch p {
+ case PhaseRunning:
+ case PhaseSuccessful:
+ case PhaseFailed:
+ case PhaseError:
+ default:
+ return fmt.Errorf("phase '%s' is not valid", p)
+ }
+ return nil
+}
diff --git a/utils/plugin/types/types.go b/utils/plugin/types/types.go
index e89ba40f2a..e3555a9ae5 100644
--- a/utils/plugin/types/types.go
+++ b/utils/plugin/types/types.go
@@ -85,24 +85,65 @@ type RpcTrafficRoutingReconciler interface {
Type() string
}
-//type Plugin struct {
-// MetricProviders []PluginItem `json:"metricProviders" yaml:"metricProviders"`
-// TrafficRouters []PluginItem `json:"trafficRouters" yaml:"trafficRouters"`
-//}
+type RpcStep interface {
+ // Run executes a step plugin for the RpcStepContext and returns the result to the controller or an RpcError for unexpeted failures
+ Run(*v1alpha1.Rollout, *RpcStepContext) (RpcStepResult, RpcError)
+ // Terminate stops an uncompleted operation started by the Run operation
+ Terminate(*v1alpha1.Rollout, *RpcStepContext) (RpcStepResult, RpcError)
+ // Abort reverts the actions performed during the Run operation if necessary
+ Abort(*v1alpha1.Rollout, *RpcStepContext) (RpcStepResult, RpcError)
+ // Type returns the type of the step plugin
+ Type() string
+}
type TrafficRouterPlugins struct {
+ // TrafficRouters is the list of plugin that implements a RpcTrafficRoutingReconciler
TrafficRouters []PluginItem `json:"trafficRouterPlugins" yaml:"trafficRouterPlugins"`
}
type MetricProviderPlugins struct {
+ // MetricProviders is the list of plugin that implements a RpcMetricProvider
MetricProviders []PluginItem `json:"metricProviderPlugins" yaml:"metricProviderPlugins"`
}
+type StepPlugins struct {
+ // Steps is the list of plugin that implements a RpcStep
+ Steps []PluginItem `json:"stepPlugins" yaml:"stepPlugins"`
+}
+
+// PluginType is a type of plugin
+type PluginType string
+
+const (
+ // PluginTypeMetricProvider is the type for a MetricProvider plugin
+ PluginTypeMetricProvider PluginType = "MetricProvider"
+ // PluginTypeTrafficRouter is the type for a TrafficRouter plugin
+ PluginTypeTrafficRouter PluginType = "TrafficRouter"
+ // PluginTypeStep is the type for a Step plugin
+ PluginTypeStep PluginType = "Step"
+)
+
type PluginItem struct {
- Name string `json:"name" yaml:"name"`
+ // Name of the plugin to use in the Rollout custom resources
+ Name string `json:"name" yaml:"name"`
+ // Location of the plugin. Supports http(s):// urls and file:// prefix
Location string `json:"location" yaml:"location"`
- Sha256 string `json:"sha256" yaml:"sha256"`
-
- // Args holds command line arguments
+ // Sha256 is the checksum of the file specified at the provided Location
+ Sha256 string `json:"sha256" yaml:"sha256"`
+ // Type of the plugin
+ Type PluginType
+ // Disabled indicates if the plugin should be ignored when referenced in Rollout custom resources. Only valid for a plugin of type Step.
+ Disabled bool `json:"disabled" yaml:"disabled"`
+ // Args holds command line arguments to initialize the plugin
Args []string `json:"args" yaml:"args"`
+ // HeadersFrom holds the names of secrets where the headers should be pulled from
+ HeadersFrom []HeadersFrom `json:"headersFrom" yaml:"headersFrom"`
+}
+
+type HeadersFrom struct {
+ SecretRef SecretRef `json:"secretRef" yaml:"secretRef"`
+}
+
+type SecretRef struct {
+ Name string `json:"name" yaml:"name"`
}
diff --git a/utils/record/record.go b/utils/record/record.go
index 96be7c8582..8180981213 100644
--- a/utils/record/record.go
+++ b/utils/record/record.go
@@ -12,8 +12,6 @@ import (
"sync"
"time"
- argoinformers "github.com/argoproj/argo-rollouts/pkg/client/informers/externalversions/rollouts/v1alpha1"
- timeutil "github.com/argoproj/argo-rollouts/utils/time"
"github.com/argoproj/notifications-engine/pkg/api"
"github.com/argoproj/notifications-engine/pkg/services"
"github.com/argoproj/notifications-engine/pkg/subscriptions"
@@ -33,6 +31,9 @@ import (
"k8s.io/client-go/tools/record"
"k8s.io/kubectl/pkg/scheme"
+ argoinformers "github.com/argoproj/argo-rollouts/pkg/client/informers/externalversions/rollouts/v1alpha1"
+ timeutil "github.com/argoproj/argo-rollouts/utils/time"
+
"github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
rolloutscheme "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned/scheme"
"github.com/argoproj/argo-rollouts/utils/annotations"
diff --git a/utils/record/record_test.go b/utils/record/record_test.go
index dfea598eae..213fc456b1 100644
--- a/utils/record/record_test.go
+++ b/utils/record/record_test.go
@@ -9,12 +9,6 @@ import (
"testing"
"time"
- "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
- argofake "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned/fake"
- argoinformersfactory "github.com/argoproj/argo-rollouts/pkg/client/informers/externalversions"
- argoinformers "github.com/argoproj/argo-rollouts/pkg/client/informers/externalversions/rollouts/v1alpha1"
- "github.com/argoproj/argo-rollouts/utils/defaults"
- timeutil "github.com/argoproj/argo-rollouts/utils/time"
"github.com/argoproj/notifications-engine/pkg/api"
notificationapi "github.com/argoproj/notifications-engine/pkg/api"
"github.com/argoproj/notifications-engine/pkg/mocks"
@@ -33,6 +27,13 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
+
+ "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
+ argofake "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned/fake"
+ argoinformersfactory "github.com/argoproj/argo-rollouts/pkg/client/informers/externalversions"
+ argoinformers "github.com/argoproj/argo-rollouts/pkg/client/informers/externalversions/rollouts/v1alpha1"
+ "github.com/argoproj/argo-rollouts/utils/defaults"
+ timeutil "github.com/argoproj/argo-rollouts/utils/time"
)
var (
diff --git a/utils/replicaset/canary.go b/utils/replicaset/canary.go
old mode 100755
new mode 100644
index cf41e6baa5..49c5ed4582
--- a/utils/replicaset/canary.go
+++ b/utils/replicaset/canary.go
@@ -4,6 +4,8 @@ import (
"encoding/json"
"math"
+ "github.com/argoproj/argo-rollouts/utils/annotations"
+
log "github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -15,7 +17,7 @@ import (
const (
// EphemeralMetadataAnnotation denotes pod metadata which is ephemerally injected to canary/stable pods
- EphemeralMetadataAnnotation = "rollout.argoproj.io/ephemeral-metadata"
+ EphemeralMetadataAnnotation = annotations.RolloutLabel + "/ephemeral-metadata"
)
func allDesiredAreAvailable(rs *appsv1.ReplicaSet, desired int32) bool {
@@ -638,18 +640,26 @@ func SyncEphemeralPodMetadata(metadata *metav1.ObjectMeta, existingPodMetadata,
if existingPodMetadata != nil {
for k := range existingPodMetadata.Annotations {
if desiredPodMetadata == nil || !isMetadataStillDesired(k, desiredPodMetadata.Annotations) {
- if metadata.Annotations != nil {
- delete(metadata.Annotations, k)
- modified = true
+ if metadata.Annotations == nil {
+ continue
+ }
+ if _, ok := metadata.Annotations[k]; !ok {
+ continue
}
+ delete(metadata.Annotations, k)
+ modified = true
}
}
for k := range existingPodMetadata.Labels {
if desiredPodMetadata == nil || !isMetadataStillDesired(k, desiredPodMetadata.Labels) {
- if metadata.Labels != nil {
- delete(metadata.Labels, k)
- modified = true
+ if metadata.Labels == nil {
+ continue
}
+ if _, ok := metadata.Labels[k]; !ok {
+ continue
+ }
+ delete(metadata.Labels, k)
+ modified = true
}
}
}
diff --git a/utils/replicaset/canary_test.go b/utils/replicaset/canary_test.go
index dbe4ce5a03..a0d5900a4f 100755
--- a/utils/replicaset/canary_test.go
+++ b/utils/replicaset/canary_test.go
@@ -1339,14 +1339,19 @@ func TestSyncEphemeralPodMetadata(t *testing.T) {
"ddd": "444",
},
}
- {
- // verify modified is false if there are no changes
+ t.Run("verify modified is false if there are no changes", func(t *testing.T) {
newMetadata, modified := SyncEphemeralPodMetadata(&meta, &existing, &existing)
assert.False(t, modified)
assert.Equal(t, meta, *newMetadata)
- }
- {
- // verify we don't touch metadata that we did not inject ourselves
+ })
+ t.Run("verify modified is false if there are no actual deletions", func(t *testing.T) {
+ existingWithExtraLabel := existing.DeepCopy()
+ existingWithExtraLabel.Labels["foo"] = "bar"
+ newMetadata, modified := SyncEphemeralPodMetadata(&meta, existingWithExtraLabel, &existing)
+ assert.False(t, modified)
+ assert.Equal(t, meta, *newMetadata)
+ })
+ t.Run("verify we don't touch metadata that we did not inject ourselves", func(t *testing.T) {
desired := v1alpha1.PodTemplateMetadata{
Labels: map[string]string{
"aaa": "222",
@@ -1356,7 +1361,6 @@ func TestSyncEphemeralPodMetadata(t *testing.T) {
},
}
newMetadata, modified := SyncEphemeralPodMetadata(&meta, &existing, &desired)
- assert.True(t, modified)
expected := metav1.ObjectMeta{
Labels: map[string]string{
"aaa": "222",
@@ -1369,8 +1373,7 @@ func TestSyncEphemeralPodMetadata(t *testing.T) {
}
assert.True(t, modified)
assert.Equal(t, expected, *newMetadata)
- }
-
+ })
}
func TestGetReplicasForScaleDown(t *testing.T) {
diff --git a/utils/replicaset/replicaset.go b/utils/replicaset/replicaset.go
index b2664afd53..272449e778 100644
--- a/utils/replicaset/replicaset.go
+++ b/utils/replicaset/replicaset.go
@@ -72,6 +72,7 @@ func FindNewReplicaSet(rollout *v1alpha1.Rollout, rsList []*appsv1.ReplicaSet) *
}
}
// new ReplicaSet does not exist.
+
return nil
}
diff --git a/utils/rollout/rolloututil.go b/utils/rollout/rolloututil.go
index 0b7df7ff38..35a3bca9c3 100644
--- a/utils/rollout/rolloututil.go
+++ b/utils/rollout/rolloututil.go
@@ -4,6 +4,8 @@ import (
"fmt"
"strconv"
+ "github.com/argoproj/argo-rollouts/utils/weightutil"
+
replicasetutil "github.com/argoproj/argo-rollouts/utils/replicaset"
"github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
@@ -179,18 +181,21 @@ func CanaryStepString(c v1alpha1.CanaryStep) string {
return fmt.Sprintf("setCanaryScale{replicas: %d}", *c.SetCanaryScale.Replicas)
}
}
+ if c.Plugin != nil {
+ return fmt.Sprintf("plugin: %s", c.Plugin.Name)
+ }
return "invalid"
}
// ShouldVerifyWeight We use this to test if we should verify weights because weight verification could involve
// API calls to the cloud provider which could incur rate limiting
-func ShouldVerifyWeight(ro *v1alpha1.Rollout) bool {
+func ShouldVerifyWeight(ro *v1alpha1.Rollout, desiredWeight int32) bool {
currentStep, _ := replicasetutil.GetCurrentCanaryStep(ro)
// If we are in the middle of an update at a setWeight step, also perform weight verification.
// Note that we don't do this every reconciliation because weight verification typically involves
// API calls to the cloud provider which could incur rate limitingq
- shouldVerifyWeight := ro.Status.StableRS != "" &&
- !IsFullyPromoted(ro) &&
- currentStep != nil && currentStep.SetWeight != nil
+ shouldVerifyWeight := (ro.Status.StableRS != "" && !IsFullyPromoted(ro) && currentStep != nil && currentStep.SetWeight != nil) ||
+ (ro.Status.StableRS != "" && !IsFullyPromoted(ro) && currentStep == nil && desiredWeight == weightutil.MaxTrafficWeight(ro)) // We are at end of rollout
+
return shouldVerifyWeight
}
diff --git a/utils/rollout/rolloututil_test.go b/utils/rollout/rolloututil_test.go
index 37c1810f00..d5a09423b5 100644
--- a/utils/rollout/rolloututil_test.go
+++ b/utils/rollout/rolloututil_test.go
@@ -5,12 +5,13 @@ import (
"testing"
"time"
- "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
- "github.com/argoproj/argo-rollouts/utils/annotations"
"github.com/tj/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"
+
+ "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
+ "github.com/argoproj/argo-rollouts/utils/annotations"
)
func newCanaryRollout() *v1alpha1.Rollout {
@@ -389,6 +390,10 @@ func TestCanaryStepString(t *testing.T) {
step: v1alpha1.CanaryStep{SetCanaryScale: &v1alpha1.SetCanaryScale{Replicas: pointer.Int32Ptr(5)}},
expectedString: "setCanaryScale{replicas: 5}",
},
+ {
+ step: v1alpha1.CanaryStep{Plugin: &v1alpha1.PluginStep{Name: "foo"}},
+ expectedString: "plugin: foo",
+ },
}
for _, test := range tests {
assert.Equal(t, test.expectedString, CanaryStepString(test.step))
@@ -422,15 +427,21 @@ func TestShouldVerifyWeight(t *testing.T) {
ro.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{
SetWeight: pointer.Int32Ptr(20),
}}
- assert.Equal(t, true, ShouldVerifyWeight(ro))
+ assert.Equal(t, true, ShouldVerifyWeight(ro, 20))
ro.Status.StableRS = ""
- assert.Equal(t, false, ShouldVerifyWeight(ro))
+ assert.Equal(t, false, ShouldVerifyWeight(ro, 20))
+
+ ro.Status.StableRS = "34feab23f"
+ ro.Status.CurrentStepIndex = nil
+ ro.Spec.Strategy.Canary.Steps = nil
+ assert.Equal(t, false, ShouldVerifyWeight(ro, 20))
+ // Test when the weight is 100, because we are at end of rollout
ro.Status.StableRS = "34feab23f"
ro.Status.CurrentStepIndex = nil
ro.Spec.Strategy.Canary.Steps = nil
- assert.Equal(t, false, ShouldVerifyWeight(ro))
+ assert.Equal(t, true, ShouldVerifyWeight(ro, 100))
}
func Test_isGenerationObserved(t *testing.T) {
diff --git a/utils/template/template_test.go b/utils/template/template_test.go
index 8f493bcd58..84580c9c8a 100644
--- a/utils/template/template_test.go
+++ b/utils/template/template_test.go
@@ -5,11 +5,12 @@ import (
"testing"
"time"
- "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
+ "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
+
"github.com/stretchr/testify/assert"
)