diff --git a/igor-web/igor-web.gradle b/igor-web/igor-web.gradle index 0d74ca44c..49c4bccca 100644 --- a/igor-web/igor-web.gradle +++ b/igor-web/igor-web.gradle @@ -36,7 +36,8 @@ dependencies { implementation "com.sun.xml.bind:jaxb-core:2.3.0.1" implementation "com.sun.xml.bind:jaxb-impl:2.3.2" - implementation "com.vdurmont:semver4j:3.1.0" + implementation "com.vdurmont:semver4j" + implementation "commons-io:commons-io" testImplementation "org.springframework.boot:spring-boot-starter-test" testImplementation "org.spockframework:spock-core" diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/config/HelmConfig.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/config/HelmConfig.java new file mode 100644 index 000000000..39cfe0a0f --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/config/HelmConfig.java @@ -0,0 +1,95 @@ +/* + * Copyright 2020 Apple, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.config; + +import com.amazonaws.util.IOUtils; +import com.google.gson.Gson; +import com.netflix.spinnaker.config.OkHttpClientConfiguration; +import com.netflix.spinnaker.igor.IgorConfigurationProperties; +import com.netflix.spinnaker.igor.helm.accounts.HelmAccounts; +import com.netflix.spinnaker.igor.helm.accounts.HelmAccountsService; +import com.netflix.spinnaker.retrofit.Slf4jRetrofitLogger; +import java.io.IOException; +import java.lang.reflect.Type; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; +import retrofit.Endpoints; +import retrofit.RestAdapter; +import retrofit.client.OkClient; +import retrofit.converter.ConversionException; +import retrofit.converter.Converter; +import retrofit.converter.GsonConverter; +import retrofit.mime.TypedInput; +import retrofit.mime.TypedOutput; + +@Configuration +@ConditionalOnProperty("helm.enabled") +@Slf4j +public class HelmConfig { + @Bean + HelmAccounts helmAccounts() { + return new HelmAccounts(); + } + + // Custom converter to deal with index file raw string responses + class StringConverter implements Converter { + private GsonConverter gson = new GsonConverter(new Gson()); + + @Override + public Object fromBody(TypedInput body, Type type) throws ConversionException { + // If the return type is a String, provide it as such + if (type.getTypeName().equals("java.lang.String")) { + try { + return IOUtils.toString(body.in()); + } catch (IOException e) { + throw new ConversionException("Cannot convert response to string"); + } + } else { + return gson.fromBody(body, type); + } + } + + @Override + public TypedOutput toBody(Object object) { + return gson.toBody(object); + } + } + + @Bean + HelmAccountsService helmAccountsService( + OkHttpClientConfiguration okHttpClientConfig, + IgorConfigurationProperties igorConfigurationProperties) { + String address = igorConfigurationProperties.getServices().getClouddriver().getBaseUrl(); + + if (StringUtils.isEmpty(address)) { + log.warn( + "No Clouddriver URL is configured - Igor will be unable to fetch Helm charts and repository indexes"); + } + + return new RestAdapter.Builder() + .setEndpoint(Endpoints.newFixedEndpoint(address)) + .setClient(new OkClient(okHttpClientConfig.create())) + .setConverter(new StringConverter()) + .setLogLevel(RestAdapter.LogLevel.BASIC) + .setLog(new Slf4jRetrofitLogger(HelmAccountsService.class)) + .build() + .create(HelmAccountsService.class); + } +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/HelmMonitor.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/HelmMonitor.java new file mode 100644 index 000000000..d1410383b --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/HelmMonitor.java @@ -0,0 +1,207 @@ +/* + * Copyright 2020 Apple, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.helm; + +import static net.logstash.logback.argument.StructuredArguments.kv; + +import com.netflix.spectator.api.Registry; +import com.netflix.spinnaker.igor.IgorConfigurationProperties; +import com.netflix.spinnaker.igor.build.model.GenericArtifact; +import com.netflix.spinnaker.igor.helm.accounts.HelmAccount; +import com.netflix.spinnaker.igor.helm.accounts.HelmAccounts; +import com.netflix.spinnaker.igor.helm.cache.HelmCache; +import com.netflix.spinnaker.igor.helm.model.HelmIndex; +import com.netflix.spinnaker.igor.history.EchoService; +import com.netflix.spinnaker.igor.history.model.HelmEvent; +import com.netflix.spinnaker.igor.polling.*; +import com.netflix.spinnaker.kork.discovery.DiscoveryStatusListener; +import com.netflix.spinnaker.kork.dynamicconfig.DynamicConfigService; +import com.netflix.spinnaker.kork.exceptions.ConstraintViolationException; +import com.netflix.spinnaker.security.AuthenticatedRequest; +import java.util.*; +import java.util.stream.Collectors; +import lombok.AllArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +@Service +@ConditionalOnProperty("helm.enabled") +public class HelmMonitor + extends CommonPollingMonitor { + private final HelmCache cache; + private final HelmAccounts helmAccounts; + private final Optional echoService; + + @Autowired + public HelmMonitor( + IgorConfigurationProperties properties, + Registry registry, + DynamicConfigService dynamicConfigService, + DiscoveryStatusListener discoveryStatusListener, + Optional lockService, + HelmCache cache, + HelmAccounts helmAccounts, + Optional echoService, + TaskScheduler scheduler) { + super( + properties, + registry, + dynamicConfigService, + discoveryStatusListener, + lockService, + scheduler); + this.cache = cache; + this.helmAccounts = helmAccounts; + this.echoService = echoService; + } + + @Override + public void poll(boolean sendEvents) { + helmAccounts.updateAccounts(); + helmAccounts.accounts.forEach( + account -> pollSingle(new PollContext(account.name, account.toMap(), !sendEvents))); + } + + @Override + public PollContext getPollContext(String partition) { + Optional account = + helmAccounts.accounts.stream().filter(it -> it.name.equals(partition)).findFirst(); + if (!account.isPresent()) { + throw new ConstraintViolationException( + String.format("Cannot find Helm account named '%s'", partition)); + } + return new PollContext(account.get().name, account.get().toMap()); + } + + @Override + protected HelmPollingDelta generateDelta(PollContext ctx) { + final String account = ctx.partitionName; + log.info("Checking for new Helm Charts {}", kv("account", account)); + + List deltas = new ArrayList<>(); + + HelmIndex index = helmAccounts.getIndex(account); + if (index == null) { + log.error("Failed to fetch Helm index {}", kv("account", account)); + } else { + Set cachedCharts = cache.getChartDigests(account); + + // If we have no cache at all, do not fire any trigger events + // This is so we don't trigger every chart version if Redis dies + boolean eventable = !cachedCharts.isEmpty(); + + index.entries.forEach( + (key, charts) -> + charts.forEach( + chartEntry -> { + if (!StringUtils.isEmpty(chartEntry.digest) + && !cachedCharts.contains(chartEntry.digest)) { + deltas.add( + new HelmDelta( + chartEntry.name, chartEntry.version, chartEntry.digest, eventable)); + } + })); + } + + if (!deltas.isEmpty()) { + List eventableDeltas = + deltas.stream().filter(it -> it.eventable).collect(Collectors.toList()); + log.info( + "Found Helm charts: {} {} {}", + kv("account", account), + kv("total", deltas.size()), + kv("eventable", eventableDeltas.size())); + } + + return new HelmPollingDelta(account, deltas); + } + + @Override + protected void commitDelta(HelmPollingDelta deltas, boolean sendEvents) { + List digests = deltas.items.stream().map(i -> i.digest).collect(Collectors.toList()); + + // Cache results + cache.cacheChartDigests(deltas.account, digests); + + // Send events, if needed + deltas.items.forEach( + item -> { + if (sendEvents && item.eventable) { + sendEvent(deltas.account, item); + } + }); + + log.info( + "Last Helm poll took {} ms {}", + System.currentTimeMillis() - deltas.startTime, + kv("account", deltas.account)); + } + + @Override + public String getName() { + return "helmMonitor"; + } + + private void sendEvent(String account, HelmDelta delta) { + if (!echoService.isPresent()) { + log.warn("Cannot send Helm notification: Echo is not enabled"); + registry + .counter(missedNotificationId.withTag("monitor", getClass().getSimpleName())) + .increment(); + return; + } + + log.info( + "Sending trigger event for {}:{} {}", delta.name, delta.version, kv("account", account)); + GenericArtifact helmArtifact = + new GenericArtifact("helm/chart", delta.name, delta.version, account); + + HelmEvent.Content helmContent = + new HelmEvent.Content(account, delta.name, delta.version, delta.digest); + + AuthenticatedRequest.allowAnonymous( + () -> echoService.get().postEvent(new HelmEvent(helmContent, helmArtifact))); + } + + @AllArgsConstructor + protected static class HelmDelta implements DeltaItem { + final String name; + final String version; + final String digest; + final boolean eventable; + } + + protected static class HelmPollingDelta implements PollingDelta { + final String account; + final List items; + final Long startTime; + + public HelmPollingDelta(String account, List items) { + this.account = account; + this.items = items; + startTime = System.currentTimeMillis(); + } + + @Override + public List getItems() { + return items; + } + } +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/accounts/ArtifactAccount.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/accounts/ArtifactAccount.java new file mode 100644 index 000000000..ec3116112 --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/accounts/ArtifactAccount.java @@ -0,0 +1,24 @@ +/* + * Copyright 2020 Apple, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.helm.accounts; + +import java.util.List; + +public class ArtifactAccount { + public String name; + public List types; +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/accounts/HelmAccount.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/accounts/HelmAccount.java new file mode 100644 index 000000000..33de8b18d --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/accounts/HelmAccount.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Apple, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.helm.accounts; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; + +public class HelmAccount { + public String cloudProvider = "helm"; + public String name; + + public HelmAccount(String name) { + this.name = name; + } + + public Map toMap() { + return ImmutableMap.of( + "cloudProvider", cloudProvider, + "name", name); + } +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/accounts/HelmAccounts.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/accounts/HelmAccounts.java new file mode 100644 index 000000000..9de839758 --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/accounts/HelmAccounts.java @@ -0,0 +1,69 @@ +/* + * Copyright 2020 Apple, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.helm.accounts; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.netflix.spinnaker.igor.helm.model.HelmIndex; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import retrofit.RetrofitError; + +@Slf4j +public class HelmAccounts { + private static final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + + @Autowired HelmAccountsService service; + + public List accounts; + + public HelmAccounts() { + this.accounts = new ArrayList<>(); + } + + public HelmIndex getIndex(String account) { + // Fetch the index from Clouddriver + Map artifact = new HashMap<>(); + artifact.put("type", "helm/index"); + artifact.put("artifactAccount", account); + + try { + String yml = service.getIndex(artifact); + return mapper.readValue(yml, HelmIndex.class); + } catch (Exception e) { + log.error("Failed to parse Helm index:", e); + return null; + } + } + + public void updateAccounts() { + try { + this.accounts = + service.getAllAccounts().stream() + .filter(it -> it.types.contains("helm/chart")) + .map(it -> new HelmAccount(it.name)) + .collect(Collectors.toList()); + } catch (RetrofitError e) { + log.error("Failed to get list of Helm accounts", e); + } + } +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/accounts/HelmAccountsService.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/accounts/HelmAccountsService.java new file mode 100644 index 000000000..cb6b502cd --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/accounts/HelmAccountsService.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Apple, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.helm.accounts; + +import java.util.List; +import java.util.Map; +import retrofit.http.*; + +public interface HelmAccountsService { + // Fetches Helm accounts from Clouddriver + @GET("/artifacts/credentials") + List getAllAccounts(); + + @Headers("Content-Type: application/json") + @PUT("/artifacts/fetch") + String getIndex(@Body Map body); +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/cache/HelmCache.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/cache/HelmCache.java new file mode 100644 index 000000000..42f87c054 --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/cache/HelmCache.java @@ -0,0 +1,70 @@ +/* + * Copyright 2020 Apple, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.helm.cache; + +import com.netflix.spinnaker.igor.IgorConfigurationProperties; +import com.netflix.spinnaker.kork.jedis.RedisClientDelegate; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class HelmCache { + + public static final String ID = "helm"; + + private final RedisClientDelegate redisClientDelegate; + private final IgorConfigurationProperties igorConfigurationProperties; + + @Autowired + public HelmCache( + RedisClientDelegate redisClientDelegate, + IgorConfigurationProperties igorConfigurationProperties) { + this.redisClientDelegate = redisClientDelegate; + this.igorConfigurationProperties = igorConfigurationProperties; + } + + public Set getChartDigests(String account) { + return redisClientDelegate.withCommandsClient( + c -> { + return c.smembers(makeIndexKey(account)); + }); + } + + public void cacheChartDigests(String account, List digests) { + redisClientDelegate.withPipeline( + p -> { + for (String digest : digests) { + p.sadd(makeIndexKey(account), digest); + } + redisClientDelegate.syncPipeline(p); + }); + } + + public String makeMemberKey(String account, String digest) { + return new HelmKey(prefix(), ID, account, digest).toString(); + } + + public String makeIndexKey(String account) { + return makeMemberKey(account, null); + } + + public String prefix() { + return igorConfigurationProperties.getSpinnaker().getJedis().getPrefix(); + } +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/cache/HelmKey.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/cache/HelmKey.java new file mode 100644 index 000000000..33520887a --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/cache/HelmKey.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Apple, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.helm.cache; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class HelmKey { + private final String prefix; + private final String id; + private final String account; + private final String digest; + + public String toString() { + if (digest != null) { + return String.format("%s:%s:%s:%s", prefix, id, account, digest); + } else { + return String.format("%s:%s:%s", prefix, id, account); + } + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof HelmKey && toString().equals(obj.toString()); + } +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/model/HelmEntry.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/model/HelmEntry.java new file mode 100644 index 000000000..1ca153f79 --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/model/HelmEntry.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Apple, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.helm.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class HelmEntry { + public String digest; + public String name; + public String version; +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/model/HelmIndex.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/model/HelmIndex.java new file mode 100644 index 000000000..2ccb2105c --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/helm/model/HelmIndex.java @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Apple, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.helm.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.List; +import java.util.Map; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class HelmIndex { + public String apiVersion; + public String generated; + public Map> entries; +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/history/model/HelmEvent.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/history/model/HelmEvent.java new file mode 100644 index 000000000..d7394be36 --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/history/model/HelmEvent.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Apple, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.history.model; + +import com.netflix.spinnaker.igor.build.model.GenericArtifact; +import java.util.HashMap; +import java.util.Map; + +public class HelmEvent extends Event { + public Content content; + public GenericArtifact artifact; + public Map details; + + public HelmEvent(Content content, GenericArtifact artifact) { + this.content = content; + this.artifact = artifact; + + this.details = new HashMap<>(); + this.details.put("type", "helm"); + this.details.put("source", "igor"); + } + + public static class Content { + public String account; + public String chart; + public String version; + public String digest; + + public Content(String account, String chart, String version, String digest) { + this.account = account; + this.chart = chart; + this.version = version; + this.digest = digest; + } + } +}