-
+
Inbox
-
+
Tags
diff --git a/frontend/demo/src/pages/Tags/Tag.module.scss b/frontend/demo/src/components/Tag/index.module.scss
similarity index 91%
rename from frontend/demo/src/pages/Tags/Tag.module.scss
rename to frontend/demo/src/components/Tag/index.module.scss
index b18c1dcd24..94c42ce3dc 100644
--- a/frontend/demo/src/pages/Tags/Tag.module.scss
+++ b/frontend/demo/src/components/Tag/index.module.scss
@@ -34,13 +34,9 @@
}
.closeButton {
- display: inline-block;
- padding-left: 4px;
-
- svg {
- path {
- fill: #fff;
- }
+ margin-left: 4px;
+ path {
+ fill: #fff;
}
}
diff --git a/frontend/demo/src/pages/Tags/Tag.tsx b/frontend/demo/src/components/Tag/index.tsx
similarity index 61%
rename from frontend/demo/src/pages/Tags/Tag.tsx
rename to frontend/demo/src/components/Tag/index.tsx
index fbc6495481..c951c78f88 100644
--- a/frontend/demo/src/pages/Tags/Tag.tsx
+++ b/frontend/demo/src/components/Tag/index.tsx
@@ -1,34 +1,27 @@
import React from 'react';
import {connect} from 'react-redux';
import {Tag as TagModel} from 'httpclient';
-import {TagSettings} from '../../types';
+import {Settings} from '../../reducers/data/settings';
-import close from '../../assets/images/icons/close.svg';
-import styles from './Tag.module.scss';
+import {ReactComponent as Close} from '../../assets/images/icons/close.svg';
+import styles from './index.module.scss';
import {RootState} from '../../reducers';
type TagProps = {
tag: TagModel;
expanded?: boolean;
onClick?: () => void;
- removeTagFromContact?: () => void;
+ removeTag?: () => void;
variant?: 'default' | 'light';
type?: string;
};
type tagState = {
- tagSettings: TagSettings;
+ settings: Settings;
};
-export const Tag = ({
- tag,
- expanded,
- variant,
- onClick,
- removeTagFromContact,
- tagSettings,
-}: TagProps & tagState): JSX.Element => {
- const tagColor = (tagSettings && tagSettings.colors[tag.color]) || {
+export const Tag = ({tag, expanded, variant, onClick, removeTag, settings}: TagProps & tagState): JSX.Element => {
+ const tagColor = (settings && settings.colors[tag.color]) || {
background: 'F1FAFF',
border: '1578D4',
default: '1578D4',
@@ -50,14 +43,12 @@ export const Tag = ({
return (
{tag.name}
- {removeTagFromContact && (
-
-
+ {removeTag && (
+
+
)}
@@ -67,7 +58,7 @@ export const Tag = ({
const mapStateToProps = (state: RootState) => {
return {
- tagSettings: state.data.settings,
+ settings: state.data.settings,
};
};
diff --git a/lib/typescript/httpclient/api/cookie.ts b/frontend/demo/src/cookies/cookie.ts
similarity index 100%
rename from lib/typescript/httpclient/api/cookie.ts
rename to frontend/demo/src/cookies/cookie.ts
diff --git a/lib/typescript/httpclient/api/index.ts b/frontend/demo/src/cookies/index.ts
similarity index 64%
rename from lib/typescript/httpclient/api/index.ts
rename to frontend/demo/src/cookies/index.ts
index a5b1f40baf..edadca8f5e 100644
--- a/lib/typescript/httpclient/api/index.ts
+++ b/frontend/demo/src/cookies/index.ts
@@ -1,3 +1,2 @@
-export * from './airyConfig';
export * from './cookie';
export * from './webStore';
diff --git a/lib/typescript/httpclient/api/webStore.ts b/frontend/demo/src/cookies/webStore.ts
similarity index 97%
rename from lib/typescript/httpclient/api/webStore.ts
rename to frontend/demo/src/cookies/webStore.ts
index 24e5b10f43..012f7d6e59 100644
--- a/lib/typescript/httpclient/api/webStore.ts
+++ b/frontend/demo/src/cookies/webStore.ts
@@ -1,5 +1,5 @@
import {getCookie, setCookie} from './cookie';
-import {User} from '../model/User';
+import {User} from 'httpclient';
export const storeDomainCookie = (key: string) => (token: string) => {
setCookie(key, token, document.domain);
diff --git a/frontend/demo/src/env.ts b/frontend/demo/src/env.ts
new file mode 100644
index 0000000000..d53a86c6f4
--- /dev/null
+++ b/frontend/demo/src/env.ts
@@ -0,0 +1,9 @@
+export interface Env {
+ API_HOST?: string;
+}
+
+const templatedState: Env = (window as any).AIRY_TEMPLATED_STATE || {};
+
+export const env: Env = {
+ API_HOST: templatedState.API_HOST || 'api.airy',
+};
diff --git a/frontend/demo/src/pages/Channels/index.tsx b/frontend/demo/src/pages/Channels/index.tsx
index f345a5310e..cc6f62b26f 100644
--- a/frontend/demo/src/pages/Channels/index.tsx
+++ b/frontend/demo/src/pages/Channels/index.tsx
@@ -5,7 +5,8 @@ import {RouteComponentProps} from 'react-router-dom';
import FacebookLogin from 'react-facebook-login';
import {Button} from '@airyhq/components';
-import {AiryConfig, Channel} from 'httpclient';
+import {Channel} from 'httpclient';
+import {AiryConfig} from '../../AiryConfig';
import {listChannels, exploreChannels, connectChannel, disconnectChannel} from '../../actions/channel';
import {StateModel} from '../../reducers/index';
diff --git a/frontend/demo/src/pages/Inbox/ConversationListItem/index.tsx b/frontend/demo/src/pages/Inbox/ConversationListItem/index.tsx
index 697d3cad91..e6d35dc8eb 100644
--- a/frontend/demo/src/pages/Inbox/ConversationListItem/index.tsx
+++ b/frontend/demo/src/pages/Inbox/ConversationListItem/index.tsx
@@ -3,12 +3,14 @@ import {Link} from 'react-router-dom';
import _, {connect, ConnectedProps} from 'react-redux';
import IconChannel from '../../../components/IconChannel';
+import AvatarImage from '../../../components/AvatarImage';
import {formatTimeOfMessage} from '../../../services/format/date';
import {Conversation, Message} from 'httpclient';
import {StateModel} from '../../../reducers';
import {INBOX_CONVERSATIONS_ROUTE} from '../../../routes/routes';
+import {readConversations} from '../../../actions/conversations';
import styles from './index.module.scss';
@@ -28,34 +30,35 @@ const mapStateToProps = (state: StateModel) => {
};
};
-const connector = connect(mapStateToProps, null);
+const mapDispatchToProps = {
+ readConversations,
+};
+
+const connector = connect(mapStateToProps, mapDispatchToProps);
const FormattedMessage = ({message}: FormattedMessageProps) => {
- if (message && message.content) {
- return <>{message.content.text}>;
+ if (message && message.content[0]) {
+ return <>{message.content[0].text}>;
}
return
;
};
const ConversationListItem = (props: ConversationListItemProps) => {
- const {conversation, active, style} = props;
+ const {conversation, active, style, readConversations} = props;
const participant = conversation.contact;
- const fallbackAvatar = 'https://s3.amazonaws.com/assets.airy.co/unknown.png';
-
const unread = conversation.unreadMessageCount > 0;
return (
-
+
readConversations(conversation.id)}>
-
+
diff --git a/frontend/demo/src/pages/Inbox/Messenger/ConversationMetadata/index.module.scss b/frontend/demo/src/pages/Inbox/Messenger/ConversationMetadata/index.module.scss
new file mode 100644
index 0000000000..c81b76f9cd
--- /dev/null
+++ b/frontend/demo/src/pages/Inbox/Messenger/ConversationMetadata/index.module.scss
@@ -0,0 +1,66 @@
+@import '../../../../assets/scss/colors.scss';
+@import '../../../../assets/scss/fonts.scss';
+
+.content {
+ display: flex;
+ width: 290px;
+ height: auto;
+ flex-direction: column;
+ overflow: hidden;
+ background-color: #fff;
+ margin: 16px 8px 0 8px;
+ padding: 16px;
+ border-top-left-radius: 8px;
+ border-top-right-radius: 8px;
+}
+
+.contact {
+ border-bottom: 1px solid var(--color-light-gray);
+ padding-bottom: 16px;
+}
+
+.avatarImage {
+ width: 80px;
+ height: 80px;
+ margin: 0 auto 16px auto;
+}
+
+.displayName {
+ @include font-m;
+ text-align: center;
+ color: var(--color-text-contrast);
+}
+
+.tags {
+ border-bottom: 1px solid var(--color-light-gray);
+ padding-bottom: 16px;
+}
+
+.tagsHeader {
+ display: flex;
+ margin: 8px 0;
+ justify-content: space-between;
+}
+
+.tagsHeaderTitle {
+ font-weight: bold;
+}
+
+.addTags {
+ padding: 8px;
+ width: 260px;
+}
+
+.addTagsRow {
+ display: flex;
+ margin: 4px 0;
+ justify-content: space-between;
+}
+
+.addTagsDescription {
+ margin: 8px 0;
+}
+
+.addTagsButtonRow {
+ margin: 8px 0;
+}
diff --git a/frontend/demo/src/pages/Inbox/Messenger/ConversationMetadata/index.tsx b/frontend/demo/src/pages/Inbox/Messenger/ConversationMetadata/index.tsx
new file mode 100644
index 0000000000..12297b3b66
--- /dev/null
+++ b/frontend/demo/src/pages/Inbox/Messenger/ConversationMetadata/index.tsx
@@ -0,0 +1,206 @@
+import React, {FormEvent, useEffect, useState} from 'react';
+import _, {connect, ConnectedProps} from 'react-redux';
+import {Conversation, Tag as TagModel, TagColor} from 'httpclient';
+
+import {createTag, listTags} from '../../../../actions/tags';
+import {addTagToConversation, removeTagFromConversation} from '../../../../actions/conversations';
+import AvatarImage from '../../../../components/AvatarImage';
+import ColorSelector from '../../../../components/ColorSelector';
+import Dialog from '../../../../components/Dialog';
+import {StateModel} from '../../../../reducers';
+
+import styles from './index.module.scss';
+import Tag from '../../../../components/Tag';
+import {Button, Input, LinkButton} from '@airyhq/components';
+
+type ConversationMetadataProps = {conversation: Conversation} & ConnectedProps
;
+
+const mapStateToProps = (state: StateModel) => {
+ return {
+ tags: state.data.tags.all,
+ };
+};
+
+const mapDispatchToProps = {
+ createTag,
+ listTags,
+ addTagToConversation,
+ removeTagFromConversation,
+};
+
+const connector = connect(mapStateToProps, mapDispatchToProps);
+
+const ConversationMetadata = (props: ConversationMetadataProps) => {
+ const {tags, createTag, conversation, listTags, addTagToConversation, removeTagFromConversation} = props;
+ const [showTagsDialog, setShowTagsDialog] = useState(false);
+ const [color, setColor] = useState('tag-blue');
+ const [tagName, setTagName] = useState('');
+
+ useEffect(() => {
+ if (tags.length == 0) {
+ listTags();
+ }
+ }, []);
+
+ const showAddTags = () => {
+ setTagName('');
+ setShowTagsDialog(true);
+ };
+
+ const addTag = (tag: TagModel) => {
+ addTagToConversation(conversation.id, tag.id);
+ setShowTagsDialog(false);
+ };
+
+ const removeTag = (tag: TagModel) => {
+ removeTagFromConversation(conversation.id, tag.id);
+ };
+
+ const filterForUnusedTags = (tags: TagModel[]): TagModel[] => {
+ return tags.filter(tag => !conversation.tags.includes(tag.id));
+ };
+
+ const filterForUsedTags = (tags: TagModel[]): TagModel[] => {
+ return tags.filter(tag => conversation.tags.includes(tag.id));
+ };
+
+ const tagSorter = (tagA: TagModel, tagB: TagModel) => {
+ if (tagA.name < tagB.name) {
+ return -1;
+ }
+ if (tagA.name > tagB.name) {
+ return 1;
+ }
+
+ return 0;
+ };
+
+ const checkIfExists = (value: string) => {
+ const usedTags = filterForUsedTags(tags);
+ if (value.length == 0) {
+ return true;
+ }
+ if (usedTags.find(tag => tag.name === value)) {
+ return 'Tag already added';
+ }
+
+ return true;
+ };
+
+ const getFilterdTags = (): TagModel[] => {
+ return filterForUnusedTags(tags)
+ .sort(tagSorter)
+ .filter(tag => tag.name.startsWith(tagName));
+ };
+
+ const submitForm = (event: FormEvent) => {
+ event.preventDefault();
+ const filteredTags = getFilterdTags();
+
+ if (filteredTags.length == 1) {
+ addTag(filteredTags[0]);
+ } else if (filteredTags.length == 0 && tagName.trim().length > 0) {
+ createTag({name: tagName.trim(), color}).then((tag: TagModel) => {
+ if (tag) {
+ addTag(tag);
+ }
+ });
+ }
+ };
+
+ const renderTagsDialog = () => {
+ const filteredTags = getFilterdTags();
+
+ return (
+
+ );
+ };
+
+ const findTag = (tagId: string): TagModel => {
+ return tags.find(tag => tag.id === tagId);
+ };
+
+ return (
+
+ {conversation && (
+
+
+
+
+
{conversation.contact.displayName}
+
+
+
+
Tags
+ {showTagsDialog ? 'Close' : '+ Add Tag'}
+
+
+ {showTagsDialog && renderTagsDialog()}
+
+
+ {tags &&
+ conversation.tags
+ .map(tagId => findTag(tagId))
+ .sort(tagSorter)
+ .map(tag => tag && removeTag(tag)} />)}
+
+
+
+ )}
+
+ );
+};
+
+export default connector(ConversationMetadata);
diff --git a/frontend/demo/src/pages/Inbox/Messenger/MessageList/index.module.scss b/frontend/demo/src/pages/Inbox/Messenger/MessageList/index.module.scss
new file mode 100644
index 0000000000..c457fed0d1
--- /dev/null
+++ b/frontend/demo/src/pages/Inbox/Messenger/MessageList/index.module.scss
@@ -0,0 +1,22 @@
+@import '../../../../assets/scss/colors.scss';
+@import '../../../../assets/scss/fonts.scss';
+
+.messageList {
+ display: flex;
+ flex-direction: column;
+ padding: 16px;
+ height: 100%;
+ overflow-y: scroll;
+ flex-grow: 1;
+ overflow-x: hidden;
+}
+
+.dateHeader {
+ @include font-s;
+ margin: 8px auto;
+ padding: 4px 8px;
+ border-radius: 4px;
+ background-color: var(--color-background-gray);
+ color: var(--color-text-gray);
+ width: max-content;
+}
diff --git a/frontend/demo/src/pages/Inbox/Messenger/MessageList/index.tsx b/frontend/demo/src/pages/Inbox/Messenger/MessageList/index.tsx
new file mode 100644
index 0000000000..ff781e0a90
--- /dev/null
+++ b/frontend/demo/src/pages/Inbox/Messenger/MessageList/index.tsx
@@ -0,0 +1,97 @@
+import React, {useEffect, createRef} from 'react';
+import _, {connect, ConnectedProps} from 'react-redux';
+import _redux from 'redux';
+
+import {Conversation, Message, SenderType} from 'httpclient';
+
+import {StateModel} from '../../../../reducers';
+import {MessageById} from '../../../../reducers/data/messages';
+
+import MessageListItem from '../MessengerListItem';
+
+import {listMessages} from '../../../../actions/messages';
+
+import styles from './index.module.scss';
+import {formatDateOfMessage} from '../../../../services/format/date';
+
+type MessageListProps = {conversation: Conversation} & ConnectedProps;
+
+const messagesMapToArray = (
+ messageInfo: {[conversationId: string]: MessageById},
+ conversationId: string
+): Message[] => {
+ const messageById = messageInfo[conversationId];
+ if (messageById) {
+ return Object.keys(messageById).map((cId: string) => ({...messageById[cId]}));
+ }
+ return [];
+};
+
+const mapStateToProps = (state: StateModel, ownProps: {conversation: Conversation}) => {
+ return {
+ messages: messagesMapToArray(state.data.messages.all, ownProps.conversation && ownProps.conversation.id),
+ };
+};
+
+const mapDispatchToProps = {
+ listMessages,
+};
+
+const connector = connect(mapStateToProps, mapDispatchToProps);
+
+const MessageList = (props: MessageListProps) => {
+ const {listMessages, messages, conversation} = props;
+ const messageListRef = createRef();
+
+ useEffect(() => {
+ conversation && listMessages(conversation.id);
+ scrollBottom();
+ }, [conversation && conversation.id]);
+
+ const scrollBottom = () => {
+ messageListRef.current.scrollTop = messageListRef.current.scrollHeight;
+ };
+
+ const isContact = (message: Message) => message.senderType !== SenderType.appUser;
+
+ const hasDateChanged = (prevMessage: Message, message: Message) => {
+ if (prevMessage == null) {
+ return true;
+ }
+
+ return !isSameDay(prevMessage.sentAt, message.sentAt);
+ };
+
+ const isSameDay = (firstDate: Date, secondDate: Date) => {
+ return new Date(firstDate).setHours(0, 0, 0, 0) === new Date(secondDate).setHours(0, 0, 0, 0);
+ };
+
+ return (
+
+ {messages.map((message: Message, index: number) => {
+ const prevMessage = messages[index - 1];
+ const nextMessage = messages[index + 1];
+ const prevWasContact = prevMessage ? isContact(prevMessage) : false;
+ const nextIsSameUser = nextMessage ? isContact(message) == isContact(nextMessage) : false;
+
+ return (
+
+ {hasDateChanged(prevMessage, message) && (
+
+ {formatDateOfMessage(message)}
+
+ )}
+
+
+ );
+ })}
+
+ );
+};
+
+export default connector(MessageList);
diff --git a/frontend/demo/src/pages/Inbox/Messenger/MessengerContainer/index.module.scss b/frontend/demo/src/pages/Inbox/Messenger/MessengerContainer/index.module.scss
new file mode 100644
index 0000000000..49e1aa191e
--- /dev/null
+++ b/frontend/demo/src/pages/Inbox/Messenger/MessengerContainer/index.module.scss
@@ -0,0 +1,38 @@
+.messengerContainer {
+ display: flex;
+ flex: 1;
+ height: auto;
+ flex-direction: column;
+ overflow: hidden;
+ background-color: #fff;
+ margin: 16px 8px 0 8px;
+ border-top-left-radius: 8px;
+ border-top-right-radius: 8px;
+}
+
+.emptyState {
+ align-self: center;
+ margin-top: 8%;
+ margin-bottom: auto;
+ max-width: 440px;
+ h1 {
+ font-size: 20px;
+ line-height: 24px;
+ font-weight: bold;
+ color: var(--color-airy-blue);
+ margin-bottom: 8px;
+ }
+ p {
+ font-size: 16px;
+ line-height: 24px;
+ font-weight: 400;
+ color: var(--color-text-gray);
+ margin-bottom: 32px;
+ }
+ svg {
+ margin-left: 50px;
+ }
+}
+
+.notSelectedState {
+}
diff --git a/frontend/demo/src/pages/Inbox/Messenger/MessengerContainer/index.tsx b/frontend/demo/src/pages/Inbox/Messenger/MessengerContainer/index.tsx
new file mode 100644
index 0000000000..b238fa13d3
--- /dev/null
+++ b/frontend/demo/src/pages/Inbox/Messenger/MessengerContainer/index.tsx
@@ -0,0 +1,48 @@
+import React, {useEffect, useState} from 'react';
+import _, {connect, ConnectedProps} from 'react-redux';
+import {useParams} from 'react-router-dom';
+
+import {StateModel} from '../../../../reducers';
+import MessageList from '../MessageList';
+import {ReactComponent as EmptyStateImage} from '../../../../assets/images/empty-state/inbox-empty-state.svg';
+import styles from './index.module.scss';
+import ConversationMetadata from '../ConversationMetadata';
+
+const mapStateToProps = (state: StateModel) => {
+ return {
+ conversations: state.data.conversations.all.items,
+ };
+};
+
+const connector = connect(mapStateToProps, null);
+
+type MessengerContainerProps = ConnectedProps;
+
+const MessengerContainer = (props: MessengerContainerProps) => {
+ const {conversations} = props;
+ const {conversationId} = useParams<{conversationId: string}>();
+ const [currentConversation, setCurrentConversation] = useState(null);
+
+ useEffect(() => {
+ setCurrentConversation(conversations[conversationId]);
+ }, [conversationId, conversations]);
+
+ return (
+ <>
+
+ {!conversations ? (
+
+
Your conversations will appear here as soon as a contact messages you.
+
Airy Messenger only shows new conversations from the moment you connect at least one channel.
+
+
+ ) : (
+
+ )}
+
+
+ >
+ );
+};
+
+export default connector(MessengerContainer);
diff --git a/frontend/demo/src/pages/Inbox/Messenger/MessengerListItem/index.module.scss b/frontend/demo/src/pages/Inbox/Messenger/MessengerListItem/index.module.scss
new file mode 100644
index 0000000000..eac1368a90
--- /dev/null
+++ b/frontend/demo/src/pages/Inbox/Messenger/MessengerListItem/index.module.scss
@@ -0,0 +1,65 @@
+@import '../../../../assets/scss/colors.scss';
+@import '../../../../assets/scss/fonts.scss';
+
+.messageListItemContainer {
+ display: flex;
+ flex: none;
+}
+
+.messageListItem {
+ display: flex;
+ align-self: flex-end;
+ width: 100%;
+ overflow-wrap: break-word;
+ word-break: break-word;
+}
+
+.messageAvatar {
+ width: 40px;
+ height: 40px;
+ margin: 6px 8px 0 0;
+}
+
+.messageListUserContainer {
+ display: flex;
+ flex-direction: row;
+}
+
+.messageListItemUser {
+ align-self: flex-start;
+ text-align: left;
+ position: relative;
+}
+
+.messageListItemUserText {
+ display: inline-flex;
+ padding: 10px;
+ margin-top: 5px;
+ background: var(--color-background-blue);
+ color: #212428;
+ border-radius: 8px;
+}
+
+.messageListItemMember {
+ margin-top: 5px;
+ justify-content: flex-end;
+ width: 100%;
+ text-align: right;
+}
+
+.messageListItemMemberText {
+ display: inline-flex;
+ padding: 10px;
+ position: relative;
+ background: var(--color-airy-blue);
+ color: white;
+ position: relative;
+ text-align: left;
+ border-radius: 8px;
+}
+
+.messageTime {
+ @include font-s;
+ color: var(--color-text-gray);
+ margin: 4px 10px;
+}
diff --git a/frontend/demo/src/pages/Inbox/Messenger/MessengerListItem/index.tsx b/frontend/demo/src/pages/Inbox/Messenger/MessengerListItem/index.tsx
new file mode 100644
index 0000000000..fc270daa9f
--- /dev/null
+++ b/frontend/demo/src/pages/Inbox/Messenger/MessengerListItem/index.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import _ from 'redux';
+import {Message, Conversation, SenderType} from 'httpclient';
+import AvatarImage from '../../../../components/AvatarImage';
+
+import styles from './index.module.scss';
+import {formatTimeOfMessage} from '../../../../services/format/date';
+
+type MessengerListItemProps = {
+ message: Message;
+ conversation: Conversation;
+ showAvatar: boolean;
+ showSentAt: boolean;
+};
+
+const MessengerListItem = (props: MessengerListItemProps) => {
+ const {conversation, showAvatar, showSentAt, message} = props;
+ const isUser = message.senderType !== SenderType.appUser;
+
+ const messageAvatar = () => {
+ return conversation && ;
+ };
+
+ const messageText = message.content[0].text;
+
+ return (
+
+
+ {!isUser ? (
+
+
{messageText}
+ {showSentAt &&
{formatTimeOfMessage(message)}
}
+
+ ) : (
+
+
{showAvatar && messageAvatar()}
+
+
{messageText}
+ {showSentAt &&
{formatTimeOfMessage(message)}
}
+
+
+ )}
+
+
+ );
+};
+
+export default MessengerListItem;
diff --git a/frontend/demo/src/pages/Inbox/Messenger/index.scss b/frontend/demo/src/pages/Inbox/Messenger/index.module.scss
similarity index 94%
rename from frontend/demo/src/pages/Inbox/Messenger/index.scss
rename to frontend/demo/src/pages/Inbox/Messenger/index.module.scss
index e333f9d510..6c59177b52 100644
--- a/frontend/demo/src/pages/Inbox/Messenger/index.scss
+++ b/frontend/demo/src/pages/Inbox/Messenger/index.module.scss
@@ -6,7 +6,7 @@
width: 100%;
align-items: stretch;
overflow: hidden;
- margin: 72px 90px 0;
+ margin: 72px 8px 0 90px;
}
.messengerContainerMiddlePanel {
diff --git a/frontend/demo/src/pages/Inbox/Messenger/index.tsx b/frontend/demo/src/pages/Inbox/Messenger/index.tsx
index a5612743e9..0024878342 100644
--- a/frontend/demo/src/pages/Inbox/Messenger/index.tsx
+++ b/frontend/demo/src/pages/Inbox/Messenger/index.tsx
@@ -1,4 +1,4 @@
-import React, {Fragment} from 'react';
+import React from 'react';
import {Route, withRouter, Redirect, RouteComponentProps} from 'react-router-dom';
import _, {connect, ConnectedProps} from 'react-redux';
@@ -7,7 +7,8 @@ import ConversationList from '../ConversationList';
import {StateModel} from '../../../reducers';
import {AllConversationsState} from '../../../reducers/data/conversations';
-import './index.scss';
+import styles from './index.module.scss';
+import MessengerContainer from './MessengerContainer';
const mapStateToProps = (state: StateModel) => {
return {
@@ -34,16 +35,16 @@ const Messenger = (props: ConnectedProps & RouteComponentProps
}
return (
-
+
{!!conversations.items && (
-
+
)}
}
+ render={props => }
/>
);
diff --git a/frontend/demo/src/pages/Inbox/index.tsx b/frontend/demo/src/pages/Inbox/index.tsx
index ff07645e13..0d2d32ccd4 100644
--- a/frontend/demo/src/pages/Inbox/index.tsx
+++ b/frontend/demo/src/pages/Inbox/index.tsx
@@ -23,7 +23,7 @@ const mapDispatchToProps = {
const connector = connect(mapStateToProps, mapDispatchToProps);
-const MessengerContainer = (props: InboxProps & ConnectedProps) => {
+const ConversationContainer = (props: InboxProps & ConnectedProps) => {
useEffect(() => {
props.listConversations();
});
@@ -31,4 +31,4 @@ const MessengerContainer = (props: InboxProps & ConnectedProps
return ;
};
-export default connector(MessengerContainer);
+export default connector(ConversationContainer);
diff --git a/frontend/demo/src/pages/Tags/FAKESETTINGS.ts b/frontend/demo/src/pages/Tags/FAKESETTINGS.ts
index 05a31a2619..4442236d92 100644
--- a/frontend/demo/src/pages/Tags/FAKESETTINGS.ts
+++ b/frontend/demo/src/pages/Tags/FAKESETTINGS.ts
@@ -1,11 +1,12 @@
-export const fakeData = () => {
+import {Settings} from '../../reducers/data/settings';
+
+export const fakeData = (): Settings => {
return {
colors: {
'tag-green': {default: '0E764F', background: 'F5FFFB', font: '0E764F', position: 3, border: '0E764F'},
'tag-blue': {default: '1578D4', background: 'F1FAFF', font: '1578D4', position: 1, border: '1578D4'},
'tag-red': {default: 'E0243A', background: 'FFF7F9', font: 'E0243A', position: 2, border: 'E0243A'},
'tag-purple': {default: '730A80', background: 'FEF7FF', font: '730A80', position: 4, border: '730A80'},
- enabled: true,
},
};
};
diff --git a/frontend/demo/src/pages/Tags/SimpleTagForm.tsx b/frontend/demo/src/pages/Tags/SimpleTagForm.tsx
index 38da9472d4..4941ca39c3 100644
--- a/frontend/demo/src/pages/Tags/SimpleTagForm.tsx
+++ b/frontend/demo/src/pages/Tags/SimpleTagForm.tsx
@@ -8,7 +8,7 @@ import {Button, Input} from '@airyhq/components';
import Dialog from '../../components/Dialog';
import ColorSelector from '../../components/ColorSelector';
-import Tag from '../../pages/Tags/Tag';
+import Tag from '../../components/Tag';
import {Tag as TagModel, TagColor} from 'httpclient';
import styles from './SimpleTagForm.module.scss';
diff --git a/frontend/demo/src/pages/Tags/TableRow.tsx b/frontend/demo/src/pages/Tags/TableRow.tsx
index 350ee6e2e4..e786fc3664 100644
--- a/frontend/demo/src/pages/Tags/TableRow.tsx
+++ b/frontend/demo/src/pages/Tags/TableRow.tsx
@@ -7,19 +7,19 @@ import {Button, LinkButton} from '@airyhq/components';
import {ReactComponent as EditIcon} from '../../assets/images/icons/edit.svg';
import {ReactComponent as TrashIcon} from '../../assets/images/icons/trash.svg';
import ColorSelector from '../../components/ColorSelector';
-import Tag from './Tag';
+import Tag from '../../components/Tag';
import {Tag as TagModel, TagColor} from 'httpclient';
-import {TagSettings} from '../../types';
+import {Settings} from '../../reducers/data/settings';
import {RootState} from '../../reducers';
type TableRowProps = {
tag: TagModel;
- tagSettings: TagSettings;
+ settings: Settings;
showModal(label: string, id: string, name: string): void;
} & ConnectedProps;
const TableRowComponent = (props: TableRowProps) => {
- const {tag, updateTag, tagSettings, showModal} = props;
+ const {tag, updateTag, settings, showModal} = props;
const [tagState, setTagState] = useState({
edit: false,
@@ -81,9 +81,11 @@ const TableRowComponent = (props: TableRowProps) => {
[showModal, tag]
);
- const getColorValue = useCallback((color: string) => (tagSettings && tagSettings.colors[color].default) || '1578D4', [
- tagSettings,
- ]);
+ const getColorValue = useCallback(
+ (color: string) =>
+ (settings && settings.colors && settings.colors[color] && settings.colors[color].default) || '1578D4',
+ [settings]
+ );
const isEditing = tagState.edit && tagState.id === tag.id;
@@ -145,7 +147,7 @@ const TableRowComponent = (props: TableRowProps) => {
const mapStateToProps = (state: RootState) => {
return {
- tagSettings: state.data.settings,
+ settings: state.data.settings,
};
};
diff --git a/frontend/demo/src/pages/Tags/index.tsx b/frontend/demo/src/pages/Tags/index.tsx
index 318101edf4..1e9fd344f5 100644
--- a/frontend/demo/src/pages/Tags/index.tsx
+++ b/frontend/demo/src/pages/Tags/index.tsx
@@ -6,7 +6,6 @@ import {SettingsModal, LinkButton, Button, SearchField, Input} from '@airyhq/com
import plus from '../../assets/images/icons/plus.svg';
import {listTags, deleteTag, filterTags, errorTag} from '../../actions/tags';
-import {fakeSettingsAPICall} from '../../actions/settings';
import {filteredTags} from '../../selectors/tags';
import {Tag} from 'httpclient';
import {ModalType} from '../../types';
@@ -34,7 +33,6 @@ class Tags extends Component, typeof initialSta
componentDidMount() {
this.props.listTags();
- this.props.fakeSettingsAPICall();
this.props.filterTags('');
}
@@ -217,7 +215,6 @@ const mapDispatchToProps = {
deleteTag,
errorTag,
filterTags,
- fakeSettingsAPICall,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
diff --git a/frontend/demo/src/reducers/data/conversations/index.ts b/frontend/demo/src/reducers/data/conversations/index.ts
index 0a262f7fe0..81a2406e33 100644
--- a/frontend/demo/src/reducers/data/conversations/index.ts
+++ b/frontend/demo/src/reducers/data/conversations/index.ts
@@ -1,6 +1,6 @@
import {ActionType, getType} from 'typesafe-actions';
import {combineReducers} from 'redux';
-import {cloneDeep} from 'lodash-es';
+import {cloneDeep, uniq} from 'lodash-es';
import {Conversation, Message} from 'httpclient';
import {ResponseMetadataPayload} from 'httpclient/payload/ResponseMetadataPayload';
@@ -44,9 +44,6 @@ function mergeConversations(
newConversations: MergedConversation[]
): ConversationMap {
newConversations.forEach((conversation: MergedConversation) => {
- if (conversation.contact && !conversation.contact.displayName) {
- conversation.contact.displayName = `${conversation.contact.firstName} ${conversation.contact.lastName}`;
- }
if (conversation.lastMessage) {
conversation.lastMessage.sentAt = new Date(conversation.lastMessage.sentAt);
}
@@ -99,6 +96,45 @@ const initialState: AllConversationsState = {
},
};
+const addTagToConversation = (state: AllConversationsState, conversationId, tagId) => {
+ const conversation: Conversation = state.items[conversationId];
+ if (conversation) {
+ const tags: string[] = [...state.items[conversationId].tags];
+ tags.push(tagId);
+
+ return {
+ ...state,
+ items: {
+ ...state.items,
+ [conversation.id]: {
+ ...conversation,
+ tags: uniq(tags),
+ },
+ },
+ };
+ }
+
+ return state;
+};
+
+const removeTagFromConversation = (state: AllConversationsState, conversationId, tagId) => {
+ const conversation: Conversation = state.items[conversationId];
+ if (conversation) {
+ return {
+ ...state,
+ items: {
+ ...state.items,
+ [conversation.id]: {
+ ...conversation,
+ tags: conversation.tags.filter(tag => tag !== tagId),
+ },
+ },
+ };
+ }
+
+ return state;
+};
+
function allReducer(state: AllConversationsState = initialState, action: Action): AllConversationsState {
switch (action.type) {
case getType(actions.mergeConversationsAction):
@@ -122,6 +158,28 @@ function allReducer(state: AllConversationsState = initialState, action: Action)
items: setLoadingOfConversation(state.items, action.payload, true),
};
+ case getType(actions.readConversationsAction):
+ return {
+ ...state,
+ items: {
+ ...state.items,
+ [action.payload.conversationId]: {
+ ...state.items[action.payload.conversationId],
+ unreadMessageCount: 0,
+ },
+ },
+ metadata: {
+ ...state.metadata,
+ loading: false,
+ },
+ };
+
+ case getType(actions.addTagToConversationAction):
+ return addTagToConversation(state, action.payload.conversationId, action.payload.tagId);
+
+ case getType(actions.removeTagFromConversationAction):
+ return removeTagFromConversation(state, action.payload.conversationId, action.payload.tagId);
+
default:
return state;
}
diff --git a/frontend/demo/src/reducers/data/index.ts b/frontend/demo/src/reducers/data/index.ts
index 1c593309f3..3fbe7a23db 100644
--- a/frontend/demo/src/reducers/data/index.ts
+++ b/frontend/demo/src/reducers/data/index.ts
@@ -8,6 +8,7 @@ import conversations, {ConversationsState} from './conversations';
import tags from './tags';
import settings from './settings';
import channels from './channels';
+import messages, {Messages} from './messages';
export * from './channels';
export * from './conversations';
@@ -18,6 +19,7 @@ export {initialState} from './user';
export type DataState = {
user: User;
conversations: ConversationsState;
+ messages: Messages;
tags: Tags;
settings: Settings;
channels: Channel[];
@@ -26,6 +28,7 @@ export type DataState = {
const reducers: Reducer = combineReducers({
user,
conversations,
+ messages,
tags,
settings,
channels,
diff --git a/frontend/demo/src/reducers/data/messages/index.ts b/frontend/demo/src/reducers/data/messages/index.ts
new file mode 100644
index 0000000000..66bed466c5
--- /dev/null
+++ b/frontend/demo/src/reducers/data/messages/index.ts
@@ -0,0 +1,43 @@
+import {ActionType, getType} from 'typesafe-actions';
+
+import * as actions from '../../../actions/messages';
+import {Message} from 'httpclient';
+import {DataState} from '..';
+import _ from 'lodash-es';
+
+type Action = ActionType;
+
+export type MessagesState = {
+ data: DataState;
+};
+
+export type MessageById = {
+ [messageId: string]: Message;
+};
+
+export type Messages = {
+ all: {[conversationId: string]: MessageById};
+};
+
+const initialState = {
+ all: {},
+};
+
+function organiseMessages(messages: Message[]): MessageById {
+ return _.keyBy(messages, 'id');
+}
+
+export default function messagesReducer(state = initialState, action: Action): any {
+ switch (action.type) {
+ case getType(actions.loadingMessagesAction):
+ return {
+ ...state,
+ all: {
+ ...state.all,
+ [action.payload.conversationId]: organiseMessages(action.payload.messages),
+ },
+ };
+ default:
+ return state;
+ }
+}
diff --git a/frontend/demo/src/reducers/data/settings/index.ts b/frontend/demo/src/reducers/data/settings/index.ts
index ceb6d1dc26..b87264068d 100644
--- a/frontend/demo/src/reducers/data/settings/index.ts
+++ b/frontend/demo/src/reducers/data/settings/index.ts
@@ -8,12 +8,20 @@ export type SettingsState = {
data: DataState;
};
+export interface ColorSettings {
+ default: string;
+ background: string;
+ font: string;
+ position: number;
+ border: string;
+}
+
export type Settings = {
- colors: {};
+ colors: {[id: string]: ColorSettings};
};
const defaultState = {
- colors: [],
+ colors: {},
};
export default function tagsReducer(state = defaultState, action: Action): Settings {
diff --git a/frontend/demo/src/reducers/data/user/index.ts b/frontend/demo/src/reducers/data/user/index.ts
index 1db881fbab..8d21abd4e3 100644
--- a/frontend/demo/src/reducers/data/user/index.ts
+++ b/frontend/demo/src/reducers/data/user/index.ts
@@ -1,6 +1,7 @@
import {ActionType, getType} from 'typesafe-actions';
import * as actions from '../../../actions/user';
-import {getUserFromStore, storeUserData, User} from 'httpclient';
+import {User} from 'httpclient';
+import {getUserFromStore, storeUserData} from '../../../cookies';
type Action = ActionType;
diff --git a/frontend/demo/src/reducers/index.ts b/frontend/demo/src/reducers/index.ts
index 3a874c6e92..c86d3f9c02 100644
--- a/frontend/demo/src/reducers/index.ts
+++ b/frontend/demo/src/reducers/index.ts
@@ -3,7 +3,7 @@ import {ActionType, getType} from 'typesafe-actions';
import _, {CombinedState} from 'redux';
import * as authActions from '../actions/user';
-import {clearUserData} from 'httpclient';
+import {clearUserData} from '../cookies';
import data, {DataState} from './data';
diff --git a/frontend/demo/src/types/tag.ts b/frontend/demo/src/types/tag.ts
index 6cb778d004..be668108bf 100644
--- a/frontend/demo/src/types/tag.ts
+++ b/frontend/demo/src/types/tag.ts
@@ -1,19 +1,3 @@
-import {Tag} from 'httpclient';
-
-export interface ColorSettings {
- default: string;
- background: string;
- font: string;
- position: number;
- border: string;
-}
-
-export interface TagSettings {
- colors: ColorSettings[];
- enabled: boolean;
- channels: Tag[];
-}
-
export interface ErrorTag {
status: string;
data?: string;
diff --git a/infrastructure/.gitignore b/infrastructure/.gitignore
index 64b7b113e9..f0e21c63a5 100644
--- a/infrastructure/.gitignore
+++ b/infrastructure/.gitignore
@@ -2,4 +2,4 @@
.vagrant-out/
images/.vagrant/
images/.vagrant-out/
-airy.conf
+airy.yaml
diff --git a/infrastructure/Vagrantfile b/infrastructure/Vagrantfile
index 91ea0dbf75..52f9e6cea1 100644
--- a/infrastructure/Vagrantfile
+++ b/infrastructure/Vagrantfile
@@ -20,9 +20,6 @@ Vagrant.configure("2") do |config|
airy_core.vm.provision "core", type: "shell", env: {"AIRY_VERSION" => ENV['AIRY_VERSION']} do |c|
c.inline = "/vagrant/scripts/provision/core.sh"
end
- airy_core.vm.provision "conf", type: "shell", env: {"AIRY_VERSION" => ENV['AIRY_VERSION']} do |u|
- u.inline = "/vagrant/scripts/conf.sh"
- end
airy_core.trigger.before [:halt, :reload] do |stop|
stop.name = "stop"
stop.run_remote = {inline: "/vagrant/scripts/trigger/stop.sh"}
diff --git a/infrastructure/airy.conf.all b/infrastructure/airy.tpl.yaml
similarity index 62%
rename from infrastructure/airy.conf.all
rename to infrastructure/airy.tpl.yaml
index a9db0e8404..b5efdf06cf 100644
--- a/infrastructure/airy.conf.all
+++ b/infrastructure/airy.tpl.yaml
@@ -1,11 +1,11 @@
# Global configuration
global:
- appImageTag: latest
+ appImageTag: develop
containerRegistry: ghcr.io/airyhq
namespace: default
-# Configuration for the Kafka cluster
core:
apps:
+ # Configuration for the Kafka cluster
kafka:
brokers: "kafka-headless:9092"
schemaRegistryUrl: "http://schema-registry:8081"
@@ -18,8 +18,8 @@ core:
postgresql:
endpoint: "postgres:5432"
dbName: "admin"
- username: "postgresadmin"
- password: "long-random-generated-password"
+ username: "postgresadmin"
+ password: "changeme"
# Specific configurations for sources
sources:
facebook:
@@ -28,11 +28,12 @@ core:
webhookSecret: "changeme"
google:
partnerKey: "changeme"
- saFile: >
- '{"type":"service_account","project_id":"airy","private_key_id":"no","private_key":"nokey","client_email":"no","client_id":"no","auth_uri":"no","token_uri":"no","no":"no","client_x509_cert_url":"no"}'
+ saFile: '{"type":"service_account","project_id":"airy","private_key_id":"no","private_key":"nokey","client_email":"no","client_id":"no","auth_uri":"no","token_uri":"no","no":"no","client_x509_cert_url":"no"}'
twilio:
authToken: "changeme"
accountSid: "changeme"
+ webhooks:
+ name: "Airy-web"
# Specific configuration for the API apps
api:
mailFrom: "changeme"
@@ -42,3 +43,14 @@ core:
mailPassword: "changeme"
jwtSecret: "long-random-generated-jwt-secret"
allowedOrigins: "*"
+ storage:
+ s3:
+ key: "changeme"
+ secret: "changeme"
+ bucket: "changeme"
+ region: "changeme"
+ path: "path"
+ingress:
+ apiHost: api.airy
+ uiHost: demo.airy
+ chatpluginHost: chatplugin.airy
diff --git a/infrastructure/cli/BUILD b/infrastructure/cli/BUILD
index ee37f6ecec..7d1cb16920 100644
--- a/infrastructure/cli/BUILD
+++ b/infrastructure/cli/BUILD
@@ -1,11 +1,9 @@
# gazelle:prefix cli
-# gazelle:importmap_prefix infrastructure
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
go_library(
name = "cli_lib",
srcs = ["main.go"],
- importmap = "infrastructure",
importpath = "cli",
visibility = ["//visibility:private"],
deps = ["//infrastructure/cli/cmd"],
@@ -18,13 +16,30 @@ go_binary(
visibility = ["//visibility:public"],
)
-go_test(
- name = "cli_test",
- srcs = ["main_test.go"],
- data = [
- "airy",
- "//infrastructure/cli/pkg/tests/golden:golden_files",
- ],
- embed = [":cli_lib"],
- deps = ["//infrastructure/cli/pkg/tests"],
-)
+os_list = [
+ "linux",
+ "darwin",
+ "windows",
+]
+
+[
+ go_binary(
+ name = "airy_" + os,
+ out = "airy_" + os,
+ embed = [":cli_lib"],
+ goarch = "amd64",
+ goos = os,
+ visibility = ["//visibility:public"],
+ )
+ for os in os_list
+]
+
+[
+ genrule(
+ name = "airy_" + os + "_bin_rule",
+ srcs = [":airy_" + os],
+ outs = ["airy_" + os + "_bin"],
+ cmd = "cp $(SRCS) $@",
+ )
+ for os in os_list
+]
diff --git a/infrastructure/cli/cmd/BUILD b/infrastructure/cli/cmd/BUILD
index 77f0424c35..4b275c53a6 100644
--- a/infrastructure/cli/cmd/BUILD
+++ b/infrastructure/cli/cmd/BUILD
@@ -2,22 +2,20 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "cmd",
- srcs = [
- "root.go",
- "version.go",
- ],
- importmap = "infrastructure/cmd",
+ srcs = ["root.go"],
importpath = "cli/cmd",
visibility = ["//visibility:public"],
x_defs = {
- "CLIVersion": "{STABLE_VERSION}",
- "GitCommit": "{STABLE_GIT_COMMIT}",
+ "Version": "{STABLE_VERSION}",
+ "CommitSHA1": "{STABLE_GIT_COMMIT}",
},
deps = [
- "//infrastructure/cli/cmd/auth",
- "//infrastructure/cli/cmd/bootstrap",
+ "//infrastructure/cli/cmd/api",
"//infrastructure/cli/cmd/config",
- "//infrastructure/cli/cmd/demo",
+ "//infrastructure/cli/cmd/status",
+ "//infrastructure/cli/cmd/ui",
+ "@com_github_mitchellh_go_homedir//:go-homedir",
"@com_github_spf13_cobra//:cobra",
+ "@com_github_spf13_viper//:viper",
],
)
diff --git a/infrastructure/cli/cmd/auth/BUILD b/infrastructure/cli/cmd/api/BUILD
similarity index 58%
rename from infrastructure/cli/cmd/auth/BUILD
rename to infrastructure/cli/cmd/api/BUILD
index cc679d53f4..0802f7ccae 100644
--- a/infrastructure/cli/cmd/auth/BUILD
+++ b/infrastructure/cli/cmd/api/BUILD
@@ -1,14 +1,18 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
- name = "auth",
- srcs = ["auth.go"],
- importmap = "infrastructure/cmd/auth",
- importpath = "cli/cmd/auth",
+ name = "api",
+ srcs = [
+ "api.go",
+ "login.go",
+ "signup.go",
+ ],
+ importpath = "cli/cmd/api",
visibility = ["//visibility:public"],
deps = [
"//lib/go/httpclient",
"//lib/go/httpclient/payloads",
"@com_github_spf13_cobra//:cobra",
+ "@com_github_spf13_viper//:viper",
],
)
diff --git a/infrastructure/cli/cmd/api/api.go b/infrastructure/cli/cmd/api/api.go
new file mode 100644
index 0000000000..bc364ed762
--- /dev/null
+++ b/infrastructure/cli/cmd/api/api.go
@@ -0,0 +1,18 @@
+package api
+
+import (
+ "github.com/spf13/cobra"
+)
+
+// APICmd subcommand for Airy Core
+var APICmd = &cobra.Command{
+ Use: "api",
+ TraverseChildren: true,
+ Short: "Interacts with the Airy Core Platform HTTP API",
+ Long: ``,
+}
+
+func init() {
+ APICmd.AddCommand(signupCmd)
+ APICmd.AddCommand(loginCmd)
+}
diff --git a/infrastructure/cli/cmd/api/login.go b/infrastructure/cli/cmd/api/login.go
new file mode 100644
index 0000000000..4fd812a491
--- /dev/null
+++ b/infrastructure/cli/cmd/api/login.go
@@ -0,0 +1,42 @@
+package api
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/airyhq/airy/lib/go/httpclient"
+ "github.com/airyhq/airy/lib/go/httpclient/payloads"
+
+ "github.com/spf13/cobra"
+ "github.com/spf13/viper"
+)
+
+var loginCmd = &cobra.Command{
+ Use: "login",
+ Short: "Logs you in in the Airy Core Platform",
+ Long: ``,
+ Run: login,
+}
+
+func login(cmd *cobra.Command, args []string) {
+ email, _ := cmd.Flags().GetString("email")
+ password, _ := cmd.Flags().GetString("password")
+ c := httpclient.NewClient(viper.GetString("apihost"))
+
+ loginRequestPayload := payloads.LoginRequestPayload{Email: email, Password: password}
+ res, err := c.Login(loginRequestPayload)
+ if err != nil {
+ fmt.Println("could not login:", err)
+ os.Exit(1)
+ }
+ fmt.Printf("logged in correctly: %s\n", res.Token)
+
+ viper.Set("apiJWTToken", res.Token)
+ viper.WriteConfig()
+}
+
+func init() {
+ var email, password string
+ loginCmd.Flags().StringVarP(&email, "email", "e", "grace@hopper.com", "Email")
+ loginCmd.Flags().StringVarP(&password, "password", "p", "the_answer_is_42", "Password")
+}
diff --git a/infrastructure/cli/cmd/api/signup.go b/infrastructure/cli/cmd/api/signup.go
new file mode 100644
index 0000000000..6470d19878
--- /dev/null
+++ b/infrastructure/cli/cmd/api/signup.go
@@ -0,0 +1,43 @@
+package api
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/airyhq/airy/lib/go/httpclient"
+ "github.com/airyhq/airy/lib/go/httpclient/payloads"
+
+ "github.com/spf13/cobra"
+ "github.com/spf13/viper"
+)
+
+var signupCmd = &cobra.Command{
+ Use: "signup",
+ Short: "Signs users up in the Airy Core Platform",
+ Long: ``,
+ Run: signup,
+}
+
+func signup(cmd *cobra.Command, args []string) {
+ firstName, _ := cmd.Flags().GetString("firstName")
+ lastName, _ := cmd.Flags().GetString("lastName")
+ email, _ := cmd.Flags().GetString("email")
+ password, _ := cmd.Flags().GetString("password")
+ c := httpclient.NewClient(viper.GetString("apihost"))
+
+ signupRequestPayload := payloads.SignupRequestPayload{FirstName: firstName, LastName: lastName, Email: email, Password: password}
+ res, err := c.Signup(signupRequestPayload)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ fmt.Printf("user created: %s\n", res.ID)
+}
+
+func init() {
+ var firstName, lastName, email, password string
+ signupCmd.Flags().StringVarP(&firstName, "firstName", "f", "Grace", "First name")
+ signupCmd.Flags().StringVarP(&lastName, "lastName", "l", "Hopper", "Last name")
+ signupCmd.Flags().StringVarP(&email, "email", "e", "grace@hopper.com", "Email")
+ signupCmd.Flags().StringVarP(&password, "password", "p", "the_answer_is_42", "Password")
+}
diff --git a/infrastructure/cli/cmd/auth/auth.go b/infrastructure/cli/cmd/auth/auth.go
deleted file mode 100644
index 629bad9d39..0000000000
--- a/infrastructure/cli/cmd/auth/auth.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package auth
-
-import (
- "fmt"
- "log"
-
- "github.com/airyhq/airy/lib/go/httpclient"
- "github.com/airyhq/airy/lib/go/httpclient/payloads"
-
- "github.com/spf13/cobra"
-)
-
-// AuthCmd subcommand for Airy Core
-var AuthCmd = &cobra.Command{
- Use: "auth",
- TraverseChildren: true,
- Short: "Create a default user and return a JWT token",
- Long: ``,
- Run: auth,
-}
-
-func auth(cmd *cobra.Command, args []string) {
- url, _ := cmd.Flags().GetString("url")
- email, _ := cmd.Flags().GetString("email")
- password, _ := cmd.Flags().GetString("password")
- c := httpclient.NewClient()
- c.BaseURL = url
-
- loginRequestPayload := payloads.LoginRequestPayload{Email: email, Password: password}
-
- res, err := c.Login(loginRequestPayload)
- if err != nil {
- signupRequestPayload := payloads.SignupRequestPayload{FirstName: "Firstname", LastName: "Lastname", Email: email, Password: password}
- res, err := c.Signup(signupRequestPayload)
- if err != nil {
- log.Fatal(err)
- }
- fmt.Println(res.Token)
- return
- }
- fmt.Println(res.Token)
-}
-
-func init() {
- var url, email, password string
- AuthCmd.Flags().StringVarP(&url, "url", "u", "http://api.airy", "The url of the Airy API")
- AuthCmd.Flags().StringVarP(&email, "email", "e", "grace@hopper.com", "Email to use for the authentication")
- AuthCmd.Flags().StringVarP(&password, "password", "p", "the_answer_is_42", "Password to use for the authentication")
-}
diff --git a/infrastructure/cli/cmd/bootstrap/bootstrap.go b/infrastructure/cli/cmd/bootstrap/bootstrap.go
deleted file mode 100644
index 66301513d6..0000000000
--- a/infrastructure/cli/cmd/bootstrap/bootstrap.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package bootstrap
-
-import (
- "log"
-
- "github.com/spf13/cobra"
-)
-
-// ResponsePayload for receiving the request
-
-// BootstrapCmd subcommand for Airy Core
-var BootstrapCmd = &cobra.Command{
- Use: "bootstrap",
- TraverseChildren: true,
- Short: "Bootstrap Airy Core Platform locally",
- Long: `This will install the Airy Core Platform in the current directory unless you choose a different one.
- It will also try to install Vagrant and VirtualBox.`,
- Run: bootstrap,
-}
-
-func bootstrap(cmd *cobra.Command, args []string) {
- // Initialize the api request
-
- log.Println("BootstrapCmd called")
-
-}
-
-func init() {
- var imageTag string
- BootstrapCmd.Flags().StringVarP(&imageTag, "image-tag", "i", "", "The docker image tag that the Airy apps will use.")
-}
diff --git a/infrastructure/cli/cmd/config/BUILD b/infrastructure/cli/cmd/config/BUILD
index 102a335cff..8c79bdd734 100644
--- a/infrastructure/cli/cmd/config/BUILD
+++ b/infrastructure/cli/cmd/config/BUILD
@@ -2,9 +2,20 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "config",
- srcs = ["config.go"],
- importmap = "infrastructure/cmd/config",
+ srcs = [
+ "config.go",
+ "configmaps.go",
+ "parser.go",
+ ],
importpath = "cli/cmd/config",
visibility = ["//visibility:public"],
- deps = ["@com_github_spf13_cobra//:cobra"],
+ deps = [
+ "@com_github_mitchellh_go_homedir//:go-homedir",
+ "@com_github_spf13_cobra//:cobra",
+ "@in_gopkg_yaml_v2//:yaml_v2",
+ "@io_k8s_api//core/v1:core",
+ "@io_k8s_apimachinery//pkg/apis/meta/v1:meta",
+ "@io_k8s_client_go//kubernetes",
+ "@io_k8s_client_go//tools/clientcmd",
+ ],
)
diff --git a/infrastructure/cli/cmd/config/config.go b/infrastructure/cli/cmd/config/config.go
index 68fa0c78f6..ebf92fc2bc 100644
--- a/infrastructure/cli/cmd/config/config.go
+++ b/infrastructure/cli/cmd/config/config.go
@@ -2,27 +2,64 @@ package config
import (
"fmt"
+ "os"
+ "path"
+ "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
)
-// ResponsePayload for receiving the request
+var kubeConfigFile string
+var configFile string
// ConfigCmd subcommand for Airy Core
var ConfigCmd = &cobra.Command{
Use: "config",
TraverseChildren: true,
- Short: "Reloads configuration based on airy.conf",
- Long: ``,
- Run: config,
+ Short: "Manages your Airy Core Platform instance via airy.yaml",
}
-func config(cmd *cobra.Command, args []string) {
- // Initialize the api request
+func applyConfig(cmd *cobra.Command, args []string) {
+ conf, err := parseConf(configFile)
+ if err != nil {
+ fmt.Println("error parsing configuration file: ", err)
+ os.Exit(1)
+ }
- fmt.Println("ConfigCmd called")
+ if twilioApply(conf, kubeConfigFile) {
+ fmt.Println("Twilio configuration applied.")
+ }
+ if facebookApply(conf, kubeConfigFile) {
+ fmt.Println("Facebook configuration applied.")
+ }
+
+ if googleApply(conf, kubeConfigFile) {
+ fmt.Println("Google configuration applied.")
+ }
+
+ if webhooksApply(conf, kubeConfigFile) {
+ fmt.Println("Webhooks configuration applied.")
+ }
+}
+
+var applyConfigCmd = &cobra.Command{
+ Use: "apply",
+ TraverseChildren: true,
+ Short: "Applies configuration values from airy.yaml configuration to the Airy Core Platform",
+ Run: applyConfig,
}
func init() {
+ ConfigCmd.PersistentFlags().StringVar(&kubeConfigFile, "kube-config", "", "Kubernetes config file for the cluster where the Airy Core Platform is running (default \"~/.airy/kube.conf\")")
+ ConfigCmd.PersistentFlags().StringVar(&configFile, "config", "./airy.yaml", "Configuration file for the Airy Core Platform")
+ if kubeConfigFile == "" {
+ home, err := homedir.Dir()
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ kubeConfigFile = path.Join(home, ".airy/kube.conf")
+ }
+ ConfigCmd.AddCommand(applyConfigCmd)
}
diff --git a/infrastructure/cli/cmd/config/configmaps.go b/infrastructure/cli/cmd/config/configmaps.go
new file mode 100644
index 0000000000..b5a091496e
--- /dev/null
+++ b/infrastructure/cli/cmd/config/configmaps.go
@@ -0,0 +1,122 @@
+package config
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ corev1 "k8s.io/api/core/v1"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/tools/clientcmd"
+)
+
+func applyConfigMap(configMapName string, newCmData map[string]string, kubeConfigFile string, namespace string) error {
+ config, kubeConfigErr := clientcmd.BuildConfigFromFlags("", kubeConfigFile)
+ if kubeConfigErr != nil {
+ return kubeConfigErr
+ }
+
+ clientset, clientsetErr := kubernetes.NewForConfig(config)
+ if clientsetErr != nil {
+ return clientsetErr
+ }
+
+ cm, _ := clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), configMapName, v1.GetOptions{})
+
+ if cm.GetName() == "" {
+ _, err := clientset.CoreV1().ConfigMaps(namespace).Create(context.TODO(),
+ &corev1.ConfigMap{
+ ObjectMeta: v1.ObjectMeta{
+ Name: configMapName,
+ Namespace: namespace,
+ },
+ Data: newCmData,
+ }, v1.CreateOptions{})
+ return err
+ } else {
+ cm.Data = newCmData
+ _, err := clientset.CoreV1().ConfigMaps(namespace).Update(context.TODO(), cm, v1.UpdateOptions{})
+ return err
+ }
+
+}
+
+func facebookApply(airyConf airyConf, kubeConfigFile string) bool {
+ facebookConfig := airyConf.Core.Apps.Sources.Facebook
+ if facebookConfig.AppID != "" || facebookConfig.AppSecret != "" || facebookConfig.WebhookSecret != "" {
+ configMapData := make(map[string]string, 0)
+ configMapData["FACEBOOK_APP_ID"] = facebookConfig.AppID
+ configMapData["FACEBOOK_APP_SECRET"] = facebookConfig.AppSecret
+ configMapData["FACEBOOK_WEBHOOK_SECRET"] = facebookConfig.WebhookSecret
+ err := applyConfigMap("sources-facebook", configMapData, kubeConfigFile, airyConf.Global.Namespace)
+
+ if err != nil {
+ fmt.Println("unable to update configMap: ", err)
+ os.Exit(1)
+ }
+
+ return true
+ }
+
+ return false
+}
+
+func googleApply(airyConf airyConf, kubeConfigFile string) bool {
+ googleConfig := airyConf.Core.Apps.Sources.Google
+ if googleConfig.PartnerKey != "" || googleConfig.SaFile != "" {
+ configMapData := make(map[string]string, 0)
+ configMapData["GOOGLE_PARTNER_KEY"] = googleConfig.PartnerKey
+ configMapData["GOOGLE_SA_FILE"] = googleConfig.SaFile
+
+ err := applyConfigMap("sources-google", configMapData, kubeConfigFile, airyConf.Global.Namespace)
+
+ if err != nil {
+ fmt.Println("unable to update configMap: ", err)
+ os.Exit(1)
+ }
+
+ return true
+ }
+
+ return false
+}
+
+func twilioApply(airyConf airyConf, kubeConfigFile string) bool {
+ twilioConfig := airyConf.Core.Apps.Sources.Twilio
+ if twilioConfig.AccountSid != "" || twilioConfig.AuthToken != "" {
+ configMapData := make(map[string]string, 0)
+ configMapData["TWILIO_ACCOUNT_SID"] = twilioConfig.AccountSid
+ configMapData["TWILIO_AUTH_TOKEN"] = twilioConfig.AuthToken
+
+ err := applyConfigMap("sources-twilio", configMapData, kubeConfigFile, airyConf.Global.Namespace)
+
+ if err != nil {
+ fmt.Println("unable to update configMap: ", err)
+ os.Exit(1)
+ }
+
+ return true
+ }
+
+ return false
+}
+
+func webhooksApply(airyConf airyConf, kubeConfigFile string) bool {
+ webhooksConfig := airyConf.Core.Apps.Webhooks
+ if webhooksConfig.Name != "" {
+ configMapData := make(map[string]string, 0)
+ configMapData["NAME"] = webhooksConfig.Name
+
+ err := applyConfigMap("webhooks-config", configMapData, kubeConfigFile, airyConf.Global.Namespace)
+
+ if err != nil {
+ fmt.Println("unable to update configMap: ", err)
+ os.Exit(1)
+ }
+
+ return true
+ }
+
+ return false
+}
diff --git a/infrastructure/cli/cmd/config/parser.go b/infrastructure/cli/cmd/config/parser.go
new file mode 100644
index 0000000000..db95a16c8b
--- /dev/null
+++ b/infrastructure/cli/cmd/config/parser.go
@@ -0,0 +1,65 @@
+package config
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+
+ "gopkg.in/yaml.v2"
+)
+
+type globalConf struct {
+ AppImageTag string `yaml:"appImageTag"`
+ ContainerRegistry string `yaml:"containerRegistry"`
+ Namespace string `yaml:"namespace"`
+}
+
+type coreConf struct {
+ Apps struct {
+ Sources struct {
+ Twilio struct {
+ AuthToken string `yaml:"authToken"`
+ AccountSid string `yaml:"accountSid"`
+ }
+ Facebook struct {
+ AppID string `yaml:"appId"`
+ AppSecret string `yaml:"appSecret"`
+ WebhookSecret string `yaml:"webhookSecret"`
+ }
+ Google struct {
+ PartnerKey string `yaml:"partnerKey"`
+ SaFile string `yaml:"saFile"`
+ }
+ }
+ Webhooks struct {
+ Name string `yaml:"name"`
+ }
+ }
+}
+
+type airyConf struct {
+ Global globalConf
+ Core coreConf
+}
+
+func parseConf(configFile string) (airyConf, error) {
+ data, err := ioutil.ReadFile(configFile)
+ if err != nil {
+ fmt.Println("error reading configuration file: ", err)
+ os.Exit(1)
+ }
+
+ conf := airyConf{
+ Global: globalConf{
+ Namespace: "default",
+ },
+ }
+
+ err = yaml.Unmarshal(data, &conf)
+ if err != nil {
+ fmt.Println("error parsing configuration file: ", err)
+ os.Exit(1)
+ }
+
+ return conf, nil
+}
diff --git a/infrastructure/cli/cmd/root.go b/infrastructure/cli/cmd/root.go
index d7268ec9fe..777763d2bb 100644
--- a/infrastructure/cli/cmd/root.go
+++ b/infrastructure/cli/cmd/root.go
@@ -3,37 +3,123 @@ package cmd
import (
"fmt"
"os"
+ "path"
- "cli/cmd/auth"
- "cli/cmd/bootstrap"
+ "cli/cmd/api"
"cli/cmd/config"
- "cli/cmd/demo"
+ "cli/cmd/status"
+ "cli/cmd/ui"
+ homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
+ "github.com/spf13/viper"
)
-// rootCmd represents the base command when called without any subcommands
-var RootCmd = &cobra.Command{
+const cliConfigFileName = "cli.yaml"
+const cliConfigDirName = ".airy"
+
+var cliConfigFile string
+var Version string
+var CommitSHA1 string
+
+var rootCmd = &cobra.Command{
Use: "airy",
- Short: "Airy CLI",
+ Short: "airy controls your Airy Core Platform instance",
Long: ``,
TraverseChildren: true,
+ PersistentPreRun: func(cmd *cobra.Command, args []string) {
+ if cmd.Name() != "init" {
+ initConfig()
+ }
+ },
}
-// Execute adds all child commands to the root command and sets flags appropriately.
-// This is called by main.main(). It only needs to happen once to the rootCmd.
-func Execute() {
+var versionCmd = &cobra.Command{
+ Use: "version",
+ Short: "Prints version information",
+ Long: ``,
+ Run: func(cmd *cobra.Command, args []string) {
+ fmt.Printf("Version: %s, GitCommit: %s", Version, CommitSHA1)
+ },
+}
+
+var initCmd = &cobra.Command{
+ Use: "init",
+ Short: "Inits your Airy CLI configuration",
+ Long: ``,
+ Run: func(cmd *cobra.Command, args []string) {
+ home, err := homedir.Dir()
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ configDirPath := path.Join(home, cliConfigDirName)
+
+ if _, errConfigDir := os.Stat(configDirPath); os.IsNotExist(errConfigDir) {
+ errDir := os.MkdirAll(configDirPath, 0700)
+ if errDir != nil {
+ fmt.Println(errDir)
+ os.Exit(1)
+ }
+ }
+
+ err = viper.WriteConfigAs(path.Join(home, cliConfigDirName, cliConfigFileName))
+ if err != nil {
+ fmt.Println("cannot write config: ", err)
+ }
+ },
+}
- if err := RootCmd.Execute(); err != nil {
+func Execute() {
+ if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
+}
+
+func initConfig() {
+ if cliConfigFile != "" {
+ viper.SetConfigFile(cliConfigFile)
+ } else {
+ home, err := homedir.Dir()
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ viper.AddConfigPath(path.Join(home, cliConfigDirName))
+ viper.SetConfigType("yaml")
+ viper.SetConfigName(cliConfigFileName)
+ }
+ if err := viper.ReadInConfig(); err != nil {
+ if _, ok := err.(viper.ConfigFileNotFoundError); ok {
+ fmt.Println(err)
+ fmt.Println("please run airy init")
+ } else {
+ fmt.Println("invalid configuration: ", err)
+ }
+
+ os.Exit(1)
+ }
}
func init() {
- RootCmd.AddCommand(bootstrap.BootstrapCmd)
- RootCmd.AddCommand(auth.AuthCmd)
- RootCmd.AddCommand(config.ConfigCmd)
- RootCmd.AddCommand(demo.DemoCmd)
+ apiHost := ""
+ rootCmd.PersistentFlags().StringVar(&apiHost, "apihost", "http://api.airy", "Airy Core Platform HTTP API host")
+ viper.BindPFlag("apihost", rootCmd.PersistentFlags().Lookup("apihost"))
+ viper.SetDefault("apihost", "http://api.airy")
+
+ apiJWTToken := ""
+ rootCmd.PersistentFlags().StringVarP(&apiJWTToken, "apiJWTToken", "", "", "apiJWTToken")
+ rootCmd.PersistentFlags().MarkHidden("apiJWTToken")
+ viper.BindPFlag("apiJWTToken", rootCmd.PersistentFlags().Lookup("apiJWTToken"))
+ rootCmd.PersistentFlags().StringVar(&cliConfigFile, "cli-config", "", "config file (default is $HOME/.airy/cli.yaml)")
+ rootCmd.AddCommand(api.APICmd)
+ rootCmd.AddCommand(config.ConfigCmd)
+ rootCmd.AddCommand(status.StatusCmd)
+ rootCmd.AddCommand(ui.UICmd)
+ rootCmd.AddCommand(versionCmd)
+ rootCmd.AddCommand(initCmd)
}
diff --git a/infrastructure/cli/cmd/bootstrap/BUILD b/infrastructure/cli/cmd/status/BUILD
similarity index 52%
rename from infrastructure/cli/cmd/bootstrap/BUILD
rename to infrastructure/cli/cmd/status/BUILD
index d016d3af30..6ff870c55d 100644
--- a/infrastructure/cli/cmd/bootstrap/BUILD
+++ b/infrastructure/cli/cmd/status/BUILD
@@ -1,12 +1,13 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
- name = "bootstrap",
- srcs = ["bootstrap.go"],
- importmap = "infrastructure/cmd/bootstrap",
- importpath = "cli/cmd/bootstrap",
+ name = "status",
+ srcs = ["status.go"],
+ importpath = "cli/cmd/status",
visibility = ["//visibility:public"],
deps = [
+ "//lib/go/httpclient",
"@com_github_spf13_cobra//:cobra",
+ "@com_github_spf13_viper//:viper",
],
)
diff --git a/infrastructure/cli/cmd/status/status.go b/infrastructure/cli/cmd/status/status.go
new file mode 100644
index 0000000000..f39437ac89
--- /dev/null
+++ b/infrastructure/cli/cmd/status/status.go
@@ -0,0 +1,44 @@
+package status
+
+import (
+ "fmt"
+ "os"
+ "text/tabwriter"
+
+ "github.com/airyhq/airy/lib/go/httpclient"
+ "github.com/spf13/cobra"
+ "github.com/spf13/viper"
+)
+
+// StatusCmd reports the status of your Airy Core Platform
+var StatusCmd = &cobra.Command{
+ Use: "status",
+ Short: "Reports the status of your Airy Core Platform",
+ Long: ``,
+ Run: status,
+}
+
+func status(cmd *cobra.Command, args []string) {
+ c := httpclient.NewClient(viper.GetString("apihost"))
+
+ c.JWTToken = viper.GetString("apiJWTToken")
+
+ res, err := c.Config()
+
+ if err != nil {
+ fmt.Println("could not read status: ", err)
+ os.Exit(1)
+ }
+
+ w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
+
+ for k, v := range res.Components {
+ if v["enabled"].(bool) {
+ fmt.Fprintf(w, "%s\t\u2713\n", k)
+ } else {
+ fmt.Fprintf(w, "%s\t\u2717\n", k)
+ }
+ }
+
+ w.Flush()
+}
diff --git a/infrastructure/cli/cmd/demo/BUILD b/infrastructure/cli/cmd/ui/BUILD
similarity index 57%
rename from infrastructure/cli/cmd/demo/BUILD
rename to infrastructure/cli/cmd/ui/BUILD
index 7bf9222d39..52a704a950 100644
--- a/infrastructure/cli/cmd/demo/BUILD
+++ b/infrastructure/cli/cmd/ui/BUILD
@@ -1,10 +1,9 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
- name = "demo",
- srcs = ["demo.go"],
- importmap = "infrastructure/cmd/demo",
- importpath = "cli/cmd/demo",
+ name = "ui",
+ srcs = ["ui.go"],
+ importpath = "cli/cmd/ui",
visibility = ["//visibility:public"],
deps = ["@com_github_spf13_cobra//:cobra"],
)
diff --git a/infrastructure/cli/cmd/demo/demo.go b/infrastructure/cli/cmd/ui/ui.go
similarity index 68%
rename from infrastructure/cli/cmd/demo/demo.go
rename to infrastructure/cli/cmd/ui/ui.go
index 7bb8e0132c..25c7e91f35 100644
--- a/infrastructure/cli/cmd/demo/demo.go
+++ b/infrastructure/cli/cmd/ui/ui.go
@@ -1,4 +1,4 @@
-package demo
+package ui
import (
"fmt"
@@ -9,13 +9,11 @@ import (
"github.com/spf13/cobra"
)
-// ResponsePayload for receiving the request
-
-// DemoCmd subcommand for Airy Core
-var DemoCmd = &cobra.Command{
- Use: "demo",
+// UICmd opens the Airy Core Platform UI
+var UICmd = &cobra.Command{
+ Use: "ui",
TraverseChildren: true,
- Short: "Opens the demo page in the browser",
+ Short: "Opens the Airy Core Platform UI in your local browser",
Long: ``,
Run: demo,
}
@@ -23,7 +21,7 @@ var DemoCmd = &cobra.Command{
func demo(cmd *cobra.Command, args []string) {
// Initialize the api request
- url := "http://chatplugin.airy/example.html"
+ url := "http://ui.airy/"
var err error
@@ -42,6 +40,3 @@ func demo(cmd *cobra.Command, args []string) {
}
}
-
-func init() {
-}
diff --git a/infrastructure/cli/cmd/version.go b/infrastructure/cli/cmd/version.go
deleted file mode 100644
index 5a5eeefb49..0000000000
--- a/infrastructure/cli/cmd/version.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package cmd
-
-import (
- "fmt"
-
- "github.com/spf13/cobra"
-)
-
-var CLIVersion string
-var GitCommit string
-
-// StatusCmd cli kafka version
-var versionCmd = &cobra.Command{
- Use: "version",
- Short: "Return current version",
- Long: ``,
- Run: version,
-}
-
-func version(cmd *cobra.Command, args []string) {
- fmt.Printf("Version: %s, GitCommit: %s", CLIVersion, GitCommit)
-}
-
-func init() {
- RootCmd.AddCommand(versionCmd)
-}
diff --git a/infrastructure/cli/go.mod b/infrastructure/cli/go.mod
index d113097457..3fb491520e 100644
--- a/infrastructure/cli/go.mod
+++ b/infrastructure/cli/go.mod
@@ -5,9 +5,18 @@ go 1.12
require (
github.com/airyhq/airy/lib/go/httpclient v0.0.0
github.com/kr/pretty v0.2.1
- github.com/spf13/cobra v0.0.3
- github.com/spf13/viper v1.3.1
+ github.com/mitchellh/go-homedir v1.1.0
+ github.com/spf13/cobra v1.1.1
+ github.com/spf13/viper v1.7.1
+ github.com/stretchr/testify v1.6.1
+ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 // indirect
+ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect
goji.io v2.0.2+incompatible
+ gopkg.in/yaml.v2 v2.4.0
+ k8s.io/api v0.20.0
+ k8s.io/apimachinery v0.20.0
+ k8s.io/client-go v0.20.0
+
)
replace github.com/airyhq/airy/lib/go/httpclient => ../../lib/go/httpclient
diff --git a/infrastructure/cli/go.sum b/infrastructure/cli/go.sum
index a53a9ff9a8..0a4a4f41b9 100644
--- a/infrastructure/cli/go.sum
+++ b/infrastructure/cli/go.sum
@@ -1,63 +1,572 @@
-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/aws/aws-sdk-go v1.28.3 h1:FnkDp+fz4JHWUW3Ust2Wh89RpdGif077Wjis/sMrGKM=
-github.com/aws/aws-sdk-go v1.28.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
+github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
+github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
+github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
+github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
+github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
-github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
+github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
+github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
+github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY=
+github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
+github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
+github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
+github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
+github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
+github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=
+github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
-github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
+github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
-github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
+github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
+github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
+github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
-github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
-github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
+github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
+github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
+github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/viper v1.3.1 h1:5+8j8FTpnFV4nEImW/ofkzEt8VoOiLXxdYIDsB73T38=
-github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
+github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8 h1:RB0v+/pc8oMzPsN97aZYEwNuJ6ouRJ2uhjxemJ9zvrY=
-github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8/go.mod h1:IlWNj9v/13q7xFbaK4mbyzMNwrZLaWSHx/aibKIZuIg=
+github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
goji.io v2.0.2+incompatible h1:uIssv/elbKRLznFUy3Xj4+2Mz/qKhek/9aZQDUMae7c=
goji.io v2.0.2+incompatible/go.mod h1:sbqFwrtqZACxLBTQcdgVjFh54yGVCvwq8+w49MVMMIk=
-golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
-golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A=
-golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE=
+golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
+golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
+golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+k8s.io/api v0.20.0 h1:WwrYoZNM1W1aQEbyl8HNG+oWGzLpZQBlcerS9BQw9yI=
+k8s.io/api v0.20.0/go.mod h1:HyLC5l5eoS/ygQYl1BXBgFzWNlkHiAuyNAbevIn+FKg=
+k8s.io/apimachinery v0.20.0 h1:jjzbTJRXk0unNS71L7h3lxGDH/2HPxMPaQY+MjECKL8=
+k8s.io/apimachinery v0.20.0/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
+k8s.io/apimachinery v0.20.2 h1:hFx6Sbt1oG0n6DZ+g4bFt5f6BoMkOjKWsQFu077M3Vg=
+k8s.io/client-go v0.20.0 h1:Xlax8PKbZsjX4gFvNtt4F5MoJ1V5prDvCuoq9B7iax0=
+k8s.io/client-go v0.20.0/go.mod h1:4KWh/g+Ocd8KkCwKF8vUNnmqgv+EVnQDK4MBF4oB5tY=
+k8s.io/client-go v1.5.1 h1:XaX/lo2/u3/pmFau8HN+sB5C/b4dc4Dmm2eXjBH4p1E=
+k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
+k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
+k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
+k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ=
+k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
+k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
+k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw=
+k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sigs.k8s.io/structured-merge-diff/v4 v4.0.2 h1:YHQV7Dajm86OuqnIR6zAelnDWBRjo+YhYV9PmGrh1s8=
+sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
+sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
+sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
+sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/infrastructure/cli/integration/BUILD b/infrastructure/cli/integration/BUILD
new file mode 100644
index 0000000000..39ed65dabe
--- /dev/null
+++ b/infrastructure/cli/integration/BUILD
@@ -0,0 +1,31 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+ name = "integration",
+ srcs = [
+ "mockserver.go",
+ "runner.go",
+ "test_file.go",
+ ],
+ importpath = "cli/integration",
+ visibility = ["//visibility:public"],
+ deps = [
+ "@com_github_kr_pretty//:pretty",
+ "@io_goji//:goji.io",
+ "@io_goji//pat",
+ ],
+)
+
+go_test(
+ name = "integration_test",
+ srcs = [
+ "api_login_test.go",
+ "noargs_test.go",
+ "version_test.go",
+ ],
+ data = [
+ "//infrastructure/cli:airy",
+ "//infrastructure/cli/integration/golden:golden_files",
+ ],
+ embed = [":integration"],
+)
diff --git a/infrastructure/cli/integration/api_login_test.go b/infrastructure/cli/integration/api_login_test.go
new file mode 100644
index 0000000000..31ea6c5a7e
--- /dev/null
+++ b/infrastructure/cli/integration/api_login_test.go
@@ -0,0 +1,19 @@
+package integration
+
+import (
+ "testing"
+)
+
+func TestApiLogin(t *testing.T) {
+ ms := NewMockServer(t)
+
+ go func() {
+ ms.Serve()
+ }()
+
+ tests := []test{
+ {"login", []string{"api", "login", "--apihost", ms.Host, "--cli-config", "golden/cli.yaml"}, "cli.login", false},
+ }
+
+ runner(t, tests)
+}
diff --git a/infrastructure/cli/integration/golden/BUILD b/infrastructure/cli/integration/golden/BUILD
new file mode 100644
index 0000000000..b0e79c68ab
--- /dev/null
+++ b/infrastructure/cli/integration/golden/BUILD
@@ -0,0 +1,9 @@
+package(default_visibility = ["//visibility:public"])
+
+filegroup(
+ name = "golden_files",
+ srcs = glob([
+ "*.golden",
+ "*.yaml",
+ ]),
+)
diff --git a/infrastructure/cli/integration/golden/cli.login.golden b/infrastructure/cli/integration/golden/cli.login.golden
new file mode 100644
index 0000000000..44e53ecb9a
--- /dev/null
+++ b/infrastructure/cli/integration/golden/cli.login.golden
@@ -0,0 +1 @@
+logged in correctly: eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJzdWIiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJpYXQiOjE2MDc2MTk0ODksInVzZXJfaWQiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJleHAiOjE2MDc3MDU4ODl9.ZuIv_t0D358n04gamNwz3U_tkxr4IO36gXuZyU9X3e4
diff --git a/infrastructure/cli/integration/golden/cli.no-args.golden b/infrastructure/cli/integration/golden/cli.no-args.golden
new file mode 100644
index 0000000000..340de9711e
--- /dev/null
+++ b/infrastructure/cli/integration/golden/cli.no-args.golden
@@ -0,0 +1,20 @@
+airy controls your Airy Core Platform instance
+
+Usage:
+ airy [command]
+
+Available Commands:
+ api Interacts with the Airy Core Platform HTTP API
+ config Manages your Airy Core Platform instance via airy.yaml
+ help Help about any command
+ init Inits your Airy CLI configuration
+ status Reports the status of your Airy Core Platform
+ ui Opens the Airy Core Platform UI in your local browser
+ version Prints version information
+
+Flags:
+ --apihost string Airy Core Platform HTTP API host (default "http://api.airy")
+ --cli-config string config file (default is $HOME/.airy/cli.yaml)
+ -h, --help help for airy
+
+Use "airy [command] --help" for more information about a command.
diff --git a/infrastructure/cli/pkg/tests/golden/cli.version.golden b/infrastructure/cli/integration/golden/cli.version.golden
similarity index 100%
rename from infrastructure/cli/pkg/tests/golden/cli.version.golden
rename to infrastructure/cli/integration/golden/cli.version.golden
diff --git a/infrastructure/cli/integration/golden/cli.yaml b/infrastructure/cli/integration/golden/cli.yaml
new file mode 100644
index 0000000000..1fec8f3a3e
--- /dev/null
+++ b/infrastructure/cli/integration/golden/cli.yaml
@@ -0,0 +1,2 @@
+apihost: http://localhost:50926
+apijwttoken: eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJzdWIiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJpYXQiOjE2MDc2MTk0ODksInVzZXJfaWQiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJleHAiOjE2MDc3MDU4ODl9.ZuIv_t0D358n04gamNwz3U_tkxr4IO36gXuZyU9X3e4
diff --git a/infrastructure/cli/pkg/tests/golden/api.signup.golden b/infrastructure/cli/integration/golden/users.signup.golden
similarity index 100%
rename from infrastructure/cli/pkg/tests/golden/api.signup.golden
rename to infrastructure/cli/integration/golden/users.signup.golden
diff --git a/infrastructure/cli/integration/mockserver.go b/infrastructure/cli/integration/mockserver.go
new file mode 100644
index 0000000000..109685e38d
--- /dev/null
+++ b/infrastructure/cli/integration/mockserver.go
@@ -0,0 +1,54 @@
+package integration
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net"
+ "net/http"
+ "path"
+ "testing"
+
+ "goji.io"
+ "goji.io/pat"
+)
+
+type MockServer struct {
+ l net.Listener
+ Host string
+ mux *goji.Mux
+}
+
+func NewMockServer(t *testing.T) *MockServer {
+ listener, err := net.Listen("tcp", ":0")
+ if err != nil {
+ t.Fatal("mock server error: ", err)
+ }
+
+ mux := goji.NewMux()
+ mux.HandleFunc(pat.Post("/users.signup"), mockEndpoint("users.signup"))
+ mux.HandleFunc(pat.Post("/users.login"), mockEndpoint("users.signup"))
+
+ return &MockServer{
+ l: listener,
+ mux: mux,
+ Host: fmt.Sprintf("http://localhost:%d", listener.Addr().(*net.TCPAddr).Port),
+ }
+}
+
+func (ms *MockServer) Serve() {
+ http.Serve(ms.l, ms.mux)
+}
+
+func mockEndpoint(endpoint string) func(w http.ResponseWriter, r *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ data, err := ioutil.ReadFile(path.Join("golden", endpoint+".golden"))
+ if err != nil {
+ fmt.Fprint(w, err)
+ }
+ _, err = w.Write(data)
+ if err != nil {
+ log.Println(err)
+ }
+ }
+}
diff --git a/infrastructure/cli/integration/noargs_test.go b/infrastructure/cli/integration/noargs_test.go
new file mode 100644
index 0000000000..81d33b5f72
--- /dev/null
+++ b/infrastructure/cli/integration/noargs_test.go
@@ -0,0 +1,18 @@
+package integration
+
+import (
+ "testing"
+)
+
+func TestNoArgs(t *testing.T) {
+ tests := []test{
+ {"no args", []string{}, "cli.no-args", false},
+ }
+
+ go func() {
+ ms := NewMockServer(t)
+ ms.Serve()
+ }()
+
+ runner(t, tests)
+}
diff --git a/infrastructure/cli/integration/runner.go b/infrastructure/cli/integration/runner.go
new file mode 100644
index 0000000000..442dfbe771
--- /dev/null
+++ b/infrastructure/cli/integration/runner.go
@@ -0,0 +1,37 @@
+package integration
+
+import (
+ "os/exec"
+ "reflect"
+ "testing"
+)
+
+type test struct {
+ name string
+ args []string
+ golden string
+ wantErr bool
+}
+
+func runner(t *testing.T, tests []test) {
+ for _, tt := range tests {
+ t.Run(tt.name, func(testing *testing.T) {
+ cmd := exec.Command("../airy", tt.args...)
+ output, err := cmd.CombinedOutput()
+ actual := string(output)
+ if (err != nil) != tt.wantErr {
+ if tt.wantErr {
+ t.Fatalf("Test %s expected to fail but did not. Error message: %v Output: %s\n", tt.name, err, actual)
+ } else {
+ t.Fatalf("Test %s expected to pass but did not. Error message: %v Output: %s\n", tt.name, err, actual)
+ }
+ }
+ golden := NewGoldenFile(t, tt.golden)
+ expected := golden.Load()
+
+ if !reflect.DeepEqual(actual, expected) {
+ t.Fatalf("diff: %v", Diff(actual, expected))
+ }
+ })
+ }
+}
diff --git a/infrastructure/cli/pkg/tests/golden.go b/infrastructure/cli/integration/test_file.go
similarity index 88%
rename from infrastructure/cli/pkg/tests/golden.go
rename to infrastructure/cli/integration/test_file.go
index fee90e27c2..a4547b9f01 100644
--- a/infrastructure/cli/pkg/tests/golden.go
+++ b/infrastructure/cli/integration/test_file.go
@@ -1,4 +1,4 @@
-package tests
+package integration
import (
"io/ioutil"
@@ -22,7 +22,7 @@ func Diff(expected, actual interface{}) []string {
// NewGoldenFile function
func NewGoldenFile(t *testing.T, name string) *TestFile {
- return &TestFile{t: t, name: name, dir: "./pkg/tests/golden/"}
+ return &TestFile{t: t, name: name + ".golden", dir: "./golden/"}
}
func (tf *TestFile) path() string {
diff --git a/infrastructure/cli/integration/version_test.go b/infrastructure/cli/integration/version_test.go
new file mode 100644
index 0000000000..e2db0ae45e
--- /dev/null
+++ b/infrastructure/cli/integration/version_test.go
@@ -0,0 +1,13 @@
+package integration
+
+import (
+ "testing"
+)
+
+func TestVersion(t *testing.T) {
+ tests := []test{
+ {"version", []string{"version", "--cli-config", "golden/cli.yaml"}, "cli.version", false},
+ }
+
+ runner(t, tests)
+}
diff --git a/infrastructure/cli/main.go b/infrastructure/cli/main.go
index abf40a3da2..6b3302a5ac 100644
--- a/infrastructure/cli/main.go
+++ b/infrastructure/cli/main.go
@@ -5,6 +5,5 @@ import (
)
func main() {
-
cmd.Execute()
}
diff --git a/infrastructure/cli/main_test.go b/infrastructure/cli/main_test.go
deleted file mode 100644
index 7a82256b7a..0000000000
--- a/infrastructure/cli/main_test.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package main
-
-import (
- "fmt"
- "os/exec"
- "testing"
-
- airytests "cli/pkg/tests"
- "reflect"
-)
-
-const binaryName = "./airy"
-
-func TestCli(t *testing.T) {
- tests := []struct {
- name string
- args []string
- golden string
- wantErr bool
- }{
- {"no args", []string{}, "cli.no-args.golden", false},
- {"auth", []string{"auth", "--url", "http://localhost:3001"}, "cli.auth.golden", false},
- {"auth", []string{"auth", "--url", "http://localhost:3001", "--email", "example@email.com"}, "cli.auth.golden", false},
- {"auth", []string{"auth", "--url", "http://localhost:3001", "--email", "example@email.com", "--password", "examplepassword"}, "cli.auth.golden", false},
- // {"bootstrap", []string{"bootstrap"}, "cli.bootstrap.golden", false},
- // {"config", []string{"config"}, "cli.config.no-args.golden", true},
- {"version", []string{"version"}, "cli.version.golden", false},
- }
-
- go func() {
- airytests.MockServer()
- }()
-
- for _, tt := range tests {
- t.Run(tt.name, func(testing *testing.T) {
- cmd := exec.Command(binaryName, tt.args...)
- output, err := cmd.CombinedOutput()
-
- if (err != nil) != tt.wantErr {
- t.Fatalf("Test expected to fail: %t. Did the test pass: %t. Error message: %v\n", tt.wantErr, err == nil, err)
- }
- fmt.Println(output)
-
- actual := string(output)
- golden := airytests.NewGoldenFile(t, tt.golden)
- expected := golden.Load()
-
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("diff: %v", airytests.Diff(actual, expected))
- }
-
- })
-
- }
-}
diff --git a/infrastructure/cli/pkg/tests/BUILD b/infrastructure/cli/pkg/tests/BUILD
deleted file mode 100644
index 21f5fede37..0000000000
--- a/infrastructure/cli/pkg/tests/BUILD
+++ /dev/null
@@ -1,18 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(default_visibility = ["//visibility:public"])
-
-go_library(
- name = "tests",
- srcs = [
- "golden.go",
- "mockserver.go",
- ],
- importmap = "infrastructure/pkg/tests",
- importpath = "cli/pkg/tests",
- deps = [
- "@com_github_kr_pretty//:pretty",
- "@io_goji//:goji.io",
- "@io_goji//pat",
- ],
-)
diff --git a/infrastructure/cli/pkg/tests/golden/BUILD b/infrastructure/cli/pkg/tests/golden/BUILD
deleted file mode 100644
index 61fb98b9de..0000000000
--- a/infrastructure/cli/pkg/tests/golden/BUILD
+++ /dev/null
@@ -1,11 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-filegroup(
- name = "golden_files",
- srcs = [
- "api.signup.golden",
- "cli.auth.golden",
- "cli.no-args.golden",
- "cli.version.golden",
- ],
-)
diff --git a/infrastructure/cli/pkg/tests/golden/cli.auth.golden b/infrastructure/cli/pkg/tests/golden/cli.auth.golden
deleted file mode 100644
index 7511a74d6d..0000000000
--- a/infrastructure/cli/pkg/tests/golden/cli.auth.golden
+++ /dev/null
@@ -1 +0,0 @@
-eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJzdWIiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJpYXQiOjE2MDc2MTk0ODksInVzZXJfaWQiOiIxMDM1MmU3NC00MjEyLTQyODItYWM3NC0wZjU4ZjgzYzJkYjUiLCJleHAiOjE2MDc3MDU4ODl9.ZuIv_t0D358n04gamNwz3U_tkxr4IO36gXuZyU9X3e4
diff --git a/infrastructure/cli/pkg/tests/golden/cli.no-args.golden b/infrastructure/cli/pkg/tests/golden/cli.no-args.golden
deleted file mode 100644
index 52769b5c49..0000000000
--- a/infrastructure/cli/pkg/tests/golden/cli.no-args.golden
+++ /dev/null
@@ -1,17 +0,0 @@
-Airy CLI
-
-Usage:
- airy [command]
-
-Available Commands:
- auth Create a default user and return a JWT token
- bootstrap Bootstrap Airy Core Platform locally
- config Reloads configuration based on airy.conf
- demo Opens the demo page in the browser
- help Help about any command
- version Return current version
-
-Flags:
- -h, --help help for airy
-
-Use "airy [command] --help" for more information about a command.
diff --git a/infrastructure/cli/pkg/tests/mockserver.go b/infrastructure/cli/pkg/tests/mockserver.go
deleted file mode 100644
index 65c1783d88..0000000000
--- a/infrastructure/cli/pkg/tests/mockserver.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package tests
-
-import (
- "fmt"
- "io/ioutil"
- "log"
- "net/http"
- "time"
-
- "goji.io"
- "goji.io/pat"
-)
-
-// MockServer starts the local server that returns the corresponding golden files for each endpoint
-func MockServer() {
- mux := goji.NewMux()
- mux.HandleFunc(pat.Post("/users.signup"), mockUserSignupHandler)
- mux.HandleFunc(pat.Post("/users.login"), mockUserLoginHandler)
-
- log.Println("starting mock server on port localhost:3001")
- s := &http.Server{
- Addr: ":3001",
- Handler: mux,
- ReadTimeout: 10 * time.Second,
- WriteTimeout: 10 * time.Second,
- }
- err := s.ListenAndServe()
- if err != nil {
- log.Println(err)
- }
-}
-
-func mockUserSignupHandler(w http.ResponseWriter, r *http.Request) {
- data, err := ioutil.ReadFile("pkg/tests/golden/api.signup.golden")
- if err != nil {
- fmt.Fprint(w, err)
- }
- _, err = w.Write(data)
- if err != nil {
- log.Println(err)
- }
-}
-
-func mockUserLoginHandler(w http.ResponseWriter, r *http.Request) {
- data, err := ioutil.ReadFile("pkg/tests/golden/api.signup.golden")
- if err != nil {
- fmt.Fprint(w, err)
- }
- _, err = w.Write(data)
- if err != nil {
- log.Println(err)
- }
-}
diff --git a/infrastructure/controller/BUILD b/infrastructure/controller/BUILD
index 8b68346131..dbddf8602b 100644
--- a/infrastructure/controller/BUILD
+++ b/infrastructure/controller/BUILD
@@ -1,5 +1,4 @@
-# gazelle:prefix controller
-# gazelle:prefix k8s.io/kubernetes
+# gazelle:prefix github.com/airyhq/airy/infrastructure/controller
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
load("@io_bazel_rules_docker//go:image.bzl", "go_image")
load("//tools/build:container_push.bzl", "container_push")
@@ -11,8 +10,7 @@ go_library(
visibility = ["//visibility:private"],
deps = [
"//infrastructure/controller/pkg/configmap-controller",
- "//infrastructure/lib/go/k8s/handler",
- "//infrastructure/lib/go/k8s/util",
+ "@io_k8s_api//core/v1:core",
"@io_k8s_client_go//kubernetes",
"@io_k8s_client_go//tools/clientcmd",
"@io_k8s_klog//:klog",
diff --git a/infrastructure/controller/main.go b/infrastructure/controller/main.go
index 5101b7925c..1c49b437cc 100644
--- a/infrastructure/controller/main.go
+++ b/infrastructure/controller/main.go
@@ -10,8 +10,9 @@ package main
import (
"flag"
-
cm "github.com/airyhq/airy/infrastructure/controller/pkg/configmap-controller"
+ v1 "k8s.io/api/core/v1"
+ "os"
"k8s.io/klog"
@@ -20,28 +21,39 @@ import (
)
func main() {
- var kubeconfig string
+ var kubeConfig string
var master string
// Check if kubernetes configuration is provided, otherwise use serviceAccount
- flag.StringVar(&kubeconfig, "kubeconfig", "", "absolute path to the kubeconfig file")
+ flag.StringVar(&kubeConfig, "kubeconfig", "", "absolute path to the kubeConfig file")
flag.StringVar(&master, "master", "", "master url")
flag.Parse()
// Create connection
- config, err := clientcmd.BuildConfigFromFlags(master, kubeconfig)
+ config, err := clientcmd.BuildConfigFromFlags(master, kubeConfig)
if err != nil {
klog.Fatal(err)
}
- // Create clientset client
- clientset, err := kubernetes.NewForConfig(config)
+ clientSet, err := kubernetes.NewForConfig(config)
if err != nil {
klog.Fatal(err)
}
+ namespace, present := os.LookupEnv("NAMESPACE")
+ if present != true {
+ klog.Infof("Namespace not set. Defaulting to: %s", v1.NamespaceDefault)
+ namespace = v1.NamespaceDefault
+ }
+
+ labelSelector := os.Getenv("LABEL_SELECTOR")
+
// Create configMap controller
- configMapController := cm.ConfigMapController(clientset)
+ configMapController := cm.ConfigMapController(cm.Context{
+ ClientSet: clientSet,
+ Namespace: namespace,
+ LabelSelector: labelSelector,
+ })
stop := make(chan struct{})
defer close(stop)
go configMapController.Run(1, stop)
diff --git a/infrastructure/controller/pkg/configmap-controller/BUILD b/infrastructure/controller/pkg/configmap-controller/BUILD
index ef6399fb37..dc329e973e 100644
--- a/infrastructure/controller/pkg/configmap-controller/BUILD
+++ b/infrastructure/controller/pkg/configmap-controller/BUILD
@@ -1,10 +1,13 @@
-# gazelle:prefix configmap-controller
-# gazelle:prefix k8s.io/kubernetes
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "configmap-controller",
- srcs = ["configmap-controller.go"],
+ srcs = [
+ "controller.go",
+ "create.go",
+ "delete.go",
+ "update.go",
+ ],
importpath = "github.com/airyhq/airy/infrastructure/controller/pkg/configmap-controller",
visibility = ["//visibility:public"],
deps = [
diff --git a/infrastructure/controller/pkg/configmap-controller/configmap-controller.go b/infrastructure/controller/pkg/configmap-controller/configmap-controller.go
deleted file mode 100644
index edfdda7b99..0000000000
--- a/infrastructure/controller/pkg/configmap-controller/configmap-controller.go
+++ /dev/null
@@ -1,149 +0,0 @@
-package configmapController
-
-import (
- "fmt"
- "time"
-
- "github.com/airyhq/airy/infrastructure/lib/go/k8s/handler"
- "github.com/airyhq/airy/infrastructure/lib/go/k8s/util"
-
- v1 "k8s.io/api/core/v1"
-
- "k8s.io/apimachinery/pkg/fields"
- "k8s.io/apimachinery/pkg/util/runtime"
- "k8s.io/apimachinery/pkg/util/wait"
- "k8s.io/klog"
-
- "k8s.io/client-go/kubernetes"
- "k8s.io/client-go/tools/cache"
- "k8s.io/client-go/util/workqueue"
-)
-
-type Controller struct {
- indexer cache.Indexer
- queue workqueue.RateLimitingInterface
- informer cache.Controller
- clientset kubernetes.Interface
-}
-
-func NewController(queue workqueue.RateLimitingInterface, indexer cache.Indexer, informer cache.Controller, clientset kubernetes.Interface) *Controller {
- return &Controller{
- informer: informer,
- indexer: indexer,
- queue: queue,
- clientset: clientset,
- }
-}
-
-func (c *Controller) processNextItem() bool {
- // Wait until there is a new item in the working queue
- key, quit := c.queue.Get()
- if quit {
- return false
- }
- defer c.queue.Done(key)
- // Invoke the method containing the business logic
- err := c.Handle(key.(string))
- // Handle the error if something went wrong during the execution of the business logic
- c.handleErr(err, key)
- return true
-}
-
-// Handle is the business logic of the controller.
-func (c *Controller) Handle(key string) error {
- obj, exists, err := c.indexer.GetByKey(key)
- if err != nil {
- klog.Errorf("Fetching object with key %s from store failed with %v", key, err)
- return err
- }
-
- if !exists {
- fmt.Printf("Object %s does not exist anymore\n", key)
- } else {
- configmap := handler.GetConfigmapConfig(obj.(*v1.ConfigMap))
- klog.Infof("Handling change in configmap %s\n", configmap.Name)
- affectedDeployments, errGetDeployments := handler.GetAffectedDeploymentsConfigmap(c.clientset, configmap.Name, "default", "")
- if errGetDeployments != nil {
- klog.Errorf("Error retrieving affected deployments %v", errGetDeployments)
- }
- for _, affectedDeployment := range affectedDeployments {
- klog.Infof("Scheduling reload for deployment: %s", affectedDeployment)
- handler.ReloadDeployment(c.clientset, "default", affectedDeployment)
-
- }
- }
- return nil
-}
-
-// handleErr checks if an error happened and makes sure we will retry later.
-func (c *Controller) handleErr(err error, key interface{}) {
- if err == nil {
- c.queue.Forget(key)
- return
- }
-
- // This controller retries 5 times if something goes wrong. After that, it stops trying.
- if c.queue.NumRequeues(key) < 5 {
- klog.Infof("Error syncing %v: %v", key, err)
- c.queue.AddRateLimited(key)
- return
- }
-
- c.queue.Forget(key)
- // Report to an external entity that, even after several retries, we could not successfully process this key
- runtime.HandleError(err)
- klog.Infof("Dropping pod %q out of the queue: %v", key, err)
-}
-
-func (c *Controller) Run(threadiness int, stopCh chan struct{}) {
- defer runtime.HandleCrash()
-
- // Let the workers stop when we are done
- defer c.queue.ShutDown()
- klog.Info("Starting controller")
-
- go c.informer.Run(stopCh)
-
- // Wait for all involved caches to be synced, before processing items from the queue is started
- if !cache.WaitForCacheSync(stopCh, c.informer.HasSynced) {
- runtime.HandleError(fmt.Errorf("Timed out waiting for caches to sync"))
- return
- }
-
- for i := 0; i < threadiness; i++ {
- go wait.Until(c.runWorker, time.Second, stopCh)
- }
-
- <-stopCh
- klog.Info("Stopping controller")
-}
-
-func (c *Controller) runWorker() {
- for c.processNextItem() {
- }
-}
-
-// ConfigMapController for monitoring the configmaps
-func ConfigMapController(clientset kubernetes.Interface) *Controller {
-
- configMapListWatcher := cache.NewListWatchFromClient(clientset.CoreV1().RESTClient(), "configmaps", v1.NamespaceDefault, fields.Everything())
- queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
- indexer, informer := cache.NewIndexerInformer(configMapListWatcher, &v1.ConfigMap{}, 0, cache.ResourceEventHandlerFuncs{
- AddFunc: func(obj interface{}) {
- // Currently we do nothing when a new configMap is added
- klog.Infof("Added configMap: %s , sha: %s", obj.(*v1.ConfigMap).GetName(), util.GetSHAfromConfigmap(obj.(*v1.ConfigMap)))
- },
- UpdateFunc: func(old interface{}, new interface{}) {
- key, err := cache.MetaNamespaceKeyFunc(new)
- if err == nil {
- queue.Add(key)
- klog.Infof("Updated configMap %s from sha: %s to sha: %s", old.(*v1.ConfigMap).GetName(), util.GetSHAfromConfigmap(old.(*v1.ConfigMap)), util.GetSHAfromConfigmap(new.(*v1.ConfigMap)))
- }
- },
- DeleteFunc: func(obj interface{}) {
- // Currently we do nothing when a new configMap is deleted
- klog.Infof("Deleted configMap %s", obj.(*v1.ConfigMap).GetName())
- },
- }, cache.Indexers{})
- return NewController(queue, indexer, informer, clientset)
-}
diff --git a/infrastructure/controller/pkg/configmap-controller/controller.go b/infrastructure/controller/pkg/configmap-controller/controller.go
new file mode 100644
index 0000000000..c30b5a7b06
--- /dev/null
+++ b/infrastructure/controller/pkg/configmap-controller/controller.go
@@ -0,0 +1,126 @@
+package cmcontroller
+
+import (
+ "fmt"
+ "time"
+
+ v1 "k8s.io/api/core/v1"
+
+ "k8s.io/apimachinery/pkg/fields"
+ "k8s.io/apimachinery/pkg/util/runtime"
+ "k8s.io/apimachinery/pkg/util/wait"
+ "k8s.io/klog"
+
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/tools/cache"
+ "k8s.io/client-go/util/workqueue"
+)
+
+type Controller struct {
+ indexer cache.Indexer
+ queue workqueue.RateLimitingInterface
+ informer cache.Controller
+ context Context
+}
+
+type Context struct {
+ ClientSet kubernetes.Interface
+ Namespace string
+ LabelSelector string
+}
+
+type ResourceHandler interface {
+ Handle(context Context) error
+}
+
+func (c *Controller) processNextItem() bool {
+ // Wait until there is a new item in the working queue
+ handler, quit := c.queue.Get()
+ if quit {
+ return false
+ }
+ defer c.queue.Done(handler)
+ // Invoke the method containing the business logic
+ err := handler.(ResourceHandler).Handle(c.context)
+ // Handle the error if something went wrong during the execution of the business logic
+ c.handleErr(err, handler)
+ return true
+}
+
+// handleErr checks if an error happened and makes sure we will retry later.
+func (c *Controller) handleErr(err error, key interface{}) {
+ if err == nil {
+ c.queue.Forget(key)
+ return
+ }
+
+ // This controller retries 5 times if something goes wrong. After that, it stops trying.
+ if c.queue.NumRequeues(key) < 5 {
+ klog.Infof("Error syncing %v: %v", key, err)
+ c.queue.AddRateLimited(key)
+ return
+ }
+
+ c.queue.Forget(key)
+ // Report to an external entity that, even after several retries, we could not successfully process this key
+ runtime.HandleError(err)
+ klog.Infof("Dropping configmap %q out of the queue: %v", key, err)
+}
+
+func (c *Controller) Run(threadiness int, stopCh chan struct{}) {
+ defer runtime.HandleCrash()
+
+ // Let the workers stop when we are done
+ defer c.queue.ShutDown()
+ klog.Info("Starting controller")
+
+ go c.informer.Run(stopCh)
+
+ // Wait for all involved caches to be synced, before processing items from the queue is started
+ if !cache.WaitForCacheSync(stopCh, c.informer.HasSynced) {
+ runtime.HandleError(fmt.Errorf("Timed out waiting for caches to sync"))
+ return
+ }
+
+ for i := 0; i < threadiness; i++ {
+ go wait.Until(c.runWorker, time.Second, stopCh)
+ }
+
+ <-stopCh
+ klog.Info("Stopping controller")
+}
+
+func (c *Controller) runWorker() {
+ for c.processNextItem() {
+ }
+}
+
+func ConfigMapController(context Context) *Controller {
+ configMapListWatcher := cache.NewListWatchFromClient(context.ClientSet.CoreV1().RESTClient(), "configmaps", context.Namespace, fields.Everything())
+ queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
+ indexer, informer := cache.NewIndexerInformer(configMapListWatcher, &v1.ConfigMap{}, 0, cache.ResourceEventHandlerFuncs{
+ AddFunc: func(obj interface{}) {
+ queue.Add(&ResourceCreatedHandler{
+ ConfigMap: obj.(*v1.ConfigMap),
+ })
+ },
+ UpdateFunc: func(old interface{}, new interface{}) {
+ queue.Add(&ResourceUpdatedHandler{
+ ConfigMap: new.(*v1.ConfigMap),
+ OldConfigMap: old.(*v1.ConfigMap),
+ })
+ },
+ DeleteFunc: func(obj interface{}) {
+ queue.Add(&ResourceDeleteHandler{
+ ConfigMap: obj.(*v1.ConfigMap),
+ })
+ },
+ }, cache.Indexers{})
+
+ return &Controller{
+ informer: informer,
+ indexer: indexer,
+ queue: queue,
+ context: context,
+ }
+}
diff --git a/infrastructure/controller/pkg/configmap-controller/create.go b/infrastructure/controller/pkg/configmap-controller/create.go
new file mode 100644
index 0000000000..9ea049ae3e
--- /dev/null
+++ b/infrastructure/controller/pkg/configmap-controller/create.go
@@ -0,0 +1,42 @@
+package cmcontroller
+
+import (
+ "github.com/airyhq/airy/infrastructure/lib/go/k8s/handler"
+ "github.com/airyhq/airy/infrastructure/lib/go/k8s/util"
+ v1 "k8s.io/api/core/v1"
+ "k8s.io/klog"
+)
+
+type ResourceCreatedHandler struct {
+ ConfigMap *v1.ConfigMap
+}
+
+func (r ResourceCreatedHandler) Handle(ctx Context) error {
+ klog.Infof("Added configMap: %s , sha: %s", r.ConfigMap.GetName(), util.GetSHAfromConfigmap(r.ConfigMap))
+ deployments, errGetDeployments := handler.GetDeploymentsReferencingCm(ctx.ClientSet,
+ r.ConfigMap.Name, ctx.Namespace, ctx.LabelSelector)
+ if errGetDeployments != nil {
+ klog.Errorf("Error retrieving affected deployments %v", errGetDeployments)
+ return errGetDeployments
+ }
+
+ for _, deployment := range deployments {
+ if !handler.CanBeStarted(deployment, ctx.ClientSet) {
+ klog.Infof("Skipping deployment %s because it is missing config maps", deployment.Name)
+ continue
+ }
+
+ klog.Infof("Scheduling start for deployment: %s", deployment.Name)
+ if err := handler.ScaleDeployment(handler.ScaleCommand{
+ ClientSet: ctx.ClientSet,
+ Namespace: ctx.Namespace,
+ DeploymentName: deployment.Name,
+ DesiredReplicas: 1, //TODO extract from annotation
+ }); err != nil {
+ klog.Errorf("Starting deployment failed: %v", err)
+ return err
+ }
+ klog.Infof("Started deployment: %s", deployment.Name)
+ }
+ return nil
+}
diff --git a/infrastructure/controller/pkg/configmap-controller/delete.go b/infrastructure/controller/pkg/configmap-controller/delete.go
new file mode 100644
index 0000000000..0453d6428f
--- /dev/null
+++ b/infrastructure/controller/pkg/configmap-controller/delete.go
@@ -0,0 +1,37 @@
+package cmcontroller
+
+import (
+ "github.com/airyhq/airy/infrastructure/lib/go/k8s/handler"
+ "github.com/airyhq/airy/infrastructure/lib/go/k8s/util"
+ v1 "k8s.io/api/core/v1"
+ "k8s.io/klog"
+)
+
+type ResourceDeleteHandler struct {
+ ConfigMap *v1.ConfigMap
+}
+
+func (r ResourceDeleteHandler) Handle(ctx Context) error {
+ klog.Infof("Deleted configMap: %s , sha: %s", r.ConfigMap.GetName(), util.GetSHAfromConfigmap(r.ConfigMap))
+ deployments, errGetDeployments := handler.GetDeploymentsReferencingCm(ctx.ClientSet,
+ r.ConfigMap.Name, ctx.Namespace, ctx.LabelSelector)
+ if errGetDeployments != nil {
+ klog.Errorf("Error retrieving affected deployments %v", errGetDeployments)
+ return errGetDeployments
+ }
+
+ for _, deployment := range deployments {
+ klog.Infof("Scheduling stopping for deployment: %s", deployment.Name)
+ if err := handler.ScaleDeployment(handler.ScaleCommand{
+ ClientSet: ctx.ClientSet,
+ Namespace: ctx.Namespace,
+ DeploymentName: deployment.Name,
+ DesiredReplicas: 0,
+ }); err != nil {
+ klog.Errorf("Stopping deployment failed: %v", err)
+ return err
+ }
+ klog.Infof("Stopped deployment: %s", deployment.Name)
+ }
+ return nil
+}
diff --git a/infrastructure/controller/pkg/configmap-controller/update.go b/infrastructure/controller/pkg/configmap-controller/update.go
new file mode 100644
index 0000000000..7a0f07b726
--- /dev/null
+++ b/infrastructure/controller/pkg/configmap-controller/update.go
@@ -0,0 +1,33 @@
+package cmcontroller
+
+import (
+ "github.com/airyhq/airy/infrastructure/lib/go/k8s/handler"
+ "github.com/airyhq/airy/infrastructure/lib/go/k8s/util"
+ v1 "k8s.io/api/core/v1"
+ "k8s.io/klog"
+)
+
+type ResourceUpdatedHandler struct {
+ ConfigMap *v1.ConfigMap
+ OldConfigMap *v1.ConfigMap
+}
+
+func (r ResourceUpdatedHandler) Handle(ctx Context) error {
+ klog.Infof("Updated configMap %s from sha: %s to sha: %s",
+ r.ConfigMap.GetName(), util.GetSHAfromConfigmap(r.OldConfigMap), util.GetSHAfromConfigmap(r.ConfigMap))
+ deployments, errGetDeployments := handler.GetDeploymentsReferencingCm(ctx.ClientSet,
+ r.ConfigMap.Name, ctx.Namespace, ctx.LabelSelector)
+ if errGetDeployments != nil {
+ klog.Errorf("Error retrieving affected deployments %v", errGetDeployments)
+ }
+
+ for _, deployment := range deployments {
+ klog.Infof("Scheduling reload for deployment: %s", deployment.Name)
+ if err := handler.ReloadDeployment(deployment, ctx.ClientSet); err != nil {
+ klog.Errorf("Reloading deployment failed: %v", err)
+ return err
+ }
+ klog.Infof("Reloaded deployment: %s", deployment.Name)
+ }
+ return nil
+}
diff --git a/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/api.yaml b/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/api.yaml
index d6034a0064..72473ace16 100644
--- a/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/api.yaml
+++ b/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/api.yaml
@@ -11,4 +11,3 @@ data:
MAIL_PASSWORD: {{ .Values.api.mailPassword }}
JWT_SECRET: {{ randAlphaNum 128 | quote }}
ALLOWED_ORIGINS: {{ .Values.api.allowedOrigins | quote }}
-
\ No newline at end of file
diff --git a/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/sources.yaml b/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/sources.yaml
index b59397d601..382f90b348 100644
--- a/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/sources.yaml
+++ b/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/sources.yaml
@@ -1,32 +1,3 @@
-{{ $coreID := randAlphaNum 10 | lower }}
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: sources-facebook
- namespace: {{ .Values.global.namespace }}
-data:
- FACEBOOK_APP_ID: {{ .Values.sources.facebook.appId | quote }}
- FACEBOOK_APP_SECRET: {{ .Values.sources.facebook.appSecret | quote }}
- FACEBOOK_WEBHOOK_SECRET: {{ .Values.sources.facebook.webhookSecret | quote }}
----
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: sources-google
- namespace: {{ .Values.global.namespace }}
-data:
- GOOGLE_PARTNER_KEY: {{ .Values.sources.google.partnerKey }}
- GOOGLE_SA_FILE: {{ .Values.sources.google.saFile }}
----
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: sources-twilio
- namespace: {{ .Values.global.namespace }}
-data:
- TWILIO_AUTH_TOKEN: {{ .Values.sources.twilio.authToken }}
- TWILIO_ACCOUNT_SID: {{ .Values.sources.twilio.accountSid }}
----
apiVersion: v1
kind: ConfigMap
metadata:
@@ -44,4 +15,5 @@ metadata:
namespace: {{ .Values.global.namespace }}
data:
APP_IMAGE_TAG: {{ .Values.global.appImageTag }}
- CORE_ID: {{ $coreID }}
+ CORE_ID: {{ randAlphaNum 10 | lower }}
+ CHATPLUGIN_JWT_SECRET: {{ randAlphaNum 128 | quote }}
\ No newline at end of file
diff --git a/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/user-storage.yaml b/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/user-storage.yaml
new file mode 100644
index 0000000000..e1197a5d02
--- /dev/null
+++ b/infrastructure/helm-chart/charts/apps/charts/airy-config/templates/user-storage.yaml
@@ -0,0 +1,14 @@
+{{ if .Values.storage }}
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: user-storage
+data:
+ {{- if .Values.storage.s3 -}}
+ STORAGE_S3_KEY: {{ .Values.storage.s3.key }}
+ STORAGE_S3_SECRET: "{{ .Values.storage.s3.secret }}"
+ STORAGE_S3_BUCKET: {{ .Values.storage.s3.bucket }}
+ STORAGE_S3_REGION: {{ .Values.storage.s3.region }}
+ STORAGE_S3_PATH: {{ .Values.storage.s3.path }}
+ {{- end -}}
+{{ end }}
diff --git a/infrastructure/helm-chart/charts/apps/charts/api-admin/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api-admin/templates/deployment.yaml
index c85d710620..b1c26779d8 100644
--- a/infrastructure/helm-chart/charts/apps/charts/api-admin/templates/deployment.yaml
+++ b/infrastructure/helm-chart/charts/apps/charts/api-admin/templates/deployment.yaml
@@ -26,6 +26,10 @@ spec:
image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}"
imagePullPolicy: Always
env:
+ - name: KUBERNETES_NAMESPACE
+ valueFrom:
+ fieldRef:
+ fieldPath: metadata.namespace
- name: KAFKA_BROKERS
valueFrom:
configMapKeyRef:
diff --git a/infrastructure/helm-chart/charts/apps/charts/frontend-chat-plugin/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/frontend-chat-plugin/templates/deployment.yaml
index 50a59433ff..2416ffe035 100644
--- a/infrastructure/helm-chart/charts/apps/charts/frontend-chat-plugin/templates/deployment.yaml
+++ b/infrastructure/helm-chart/charts/apps/charts/frontend-chat-plugin/templates/deployment.yaml
@@ -7,7 +7,7 @@ metadata:
app: frontend-chat-plugin
type: frontend
spec:
- replicas: 0
+ replicas: 1
selector:
matchLabels:
app: frontend-chat-plugin
@@ -25,6 +25,12 @@ spec:
- name: app
image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}"
imagePullPolicy: Always
+ env:
+ - name: API_HOST
+ valueFrom:
+ configMapKeyRef:
+ name: hostnames
+ key: CHATPLUGIN_HOST
livenessProbe:
httpGet:
path: /health
diff --git a/infrastructure/helm-chart/charts/apps/charts/frontend-demo/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/frontend-demo/templates/deployment.yaml
index bec1b7c43a..4ccf3f6022 100644
--- a/infrastructure/helm-chart/charts/apps/charts/frontend-demo/templates/deployment.yaml
+++ b/infrastructure/helm-chart/charts/apps/charts/frontend-demo/templates/deployment.yaml
@@ -7,7 +7,7 @@ metadata:
app: frontend-demo
type: frontend
spec:
- replicas: 0
+ replicas: 1
selector:
matchLabels:
app: frontend-demo
@@ -25,6 +25,12 @@ spec:
- name: app
image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}"
imagePullPolicy: Always
+ env:
+ - name: API_HOST
+ valueFrom:
+ configMapKeyRef:
+ name: hostnames
+ key: API_HOST
livenessProbe:
httpGet:
path: /health
diff --git a/infrastructure/helm-chart/charts/apps/charts/media-resolver/Chart.yaml b/infrastructure/helm-chart/charts/apps/charts/media-resolver/Chart.yaml
new file mode 100644
index 0000000000..8e8e7a0e37
--- /dev/null
+++ b/infrastructure/helm-chart/charts/apps/charts/media-resolver/Chart.yaml
@@ -0,0 +1,5 @@
+apiVersion: v1
+appVersion: "1.0"
+description: A Helm chart for the Media Resolver app
+name: media-resolver
+version: 0.1.0
diff --git a/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/deployment.yaml
new file mode 100644
index 0000000000..2379d5f75b
--- /dev/null
+++ b/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/deployment.yaml
@@ -0,0 +1,58 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: media-resolver
+ namespace: default
+ labels:
+ app: media-resolver
+ type: media
+spec:
+ replicas: 0
+ selector:
+ matchLabels:
+ app: media-resolver
+ strategy:
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 1
+ type: RollingUpdate
+ template:
+ metadata:
+ labels:
+ app: media-resolver
+ spec:
+ containers:
+ - name: app
+ image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}"
+ imagePullPolicy: Always
+ envFrom:
+ - configMapRef:
+ name: user-storage
+ env:
+ - name: KAFKA_BROKERS
+ valueFrom:
+ configMapKeyRef:
+ name: kafka-config
+ key: KAFKA_BROKERS
+ - name: KAFKA_SCHEMA_REGISTRY_URL
+ valueFrom:
+ configMapKeyRef:
+ name: kafka-config
+ key: KAFKA_SCHEMA_REGISTRY_URL
+ - name: KAFKA_COMMIT_INTERVAL_MS
+ valueFrom:
+ configMapKeyRef:
+ name: kafka-config
+ key: KAFKA_COMMIT_INTERVAL_MS
+ - name: SERVICE_NAME
+ value: media-resolver
+ livenessProbe:
+ httpGet:
+ path: /actuator/health
+ port: 8080
+ httpHeaders:
+ - name: Health-Check
+ value: health-check
+ initialDelaySeconds: 60
+ periodSeconds: 10
+ failureThreshold: 3
diff --git a/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/service.yaml b/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/service.yaml
new file mode 100644
index 0000000000..8cd982a016
--- /dev/null
+++ b/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/service.yaml
@@ -0,0 +1,13 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: media-resolver
+ namespace: default
+spec:
+ ports:
+ - port: 80
+ targetPort: 8080
+ protocol: TCP
+ type: NodePort
+ selector:
+ app: media-resolver
diff --git a/infrastructure/helm-chart/charts/apps/charts/media-resolver/values.yaml b/infrastructure/helm-chart/charts/apps/charts/media-resolver/values.yaml
new file mode 100644
index 0000000000..6a2b9ac149
--- /dev/null
+++ b/infrastructure/helm-chart/charts/apps/charts/media-resolver/values.yaml
@@ -0,0 +1 @@
+image: media/resolver
diff --git a/infrastructure/helm-chart/charts/apps/charts/sources-chatplugin/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources-chatplugin/templates/deployment.yaml
index e0aab7b08e..7364879de5 100644
--- a/infrastructure/helm-chart/charts/apps/charts/sources-chatplugin/templates/deployment.yaml
+++ b/infrastructure/helm-chart/charts/apps/charts/sources-chatplugin/templates/deployment.yaml
@@ -6,8 +6,9 @@ metadata:
labels:
app: sources-chatplugin
type: sources-chatplugin
+ core.airy.co/managed: "true"
spec:
- replicas: 0
+ replicas: 1
selector:
matchLabels:
app: sources-chatplugin
@@ -34,8 +35,8 @@ spec:
- name: JWT_SECRET
valueFrom:
configMapKeyRef:
- name: api-config
- key: JWT_SECRET
+ name: core-config
+ key: CHATPLUGIN_JWT_SECRET
- name: KAFKA_BROKERS
valueFrom:
configMapKeyRef:
diff --git a/infrastructure/helm-chart/charts/apps/charts/sources-facebook-connector/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources-facebook-connector/templates/deployment.yaml
index 57755fd259..7d551079b0 100644
--- a/infrastructure/helm-chart/charts/apps/charts/sources-facebook-connector/templates/deployment.yaml
+++ b/infrastructure/helm-chart/charts/apps/charts/sources-facebook-connector/templates/deployment.yaml
@@ -6,6 +6,7 @@ metadata:
labels:
app: sources-facebook-connector
type: sources-facebook
+ core.airy.co/managed: "true"
spec:
replicas: 0
selector:
@@ -64,11 +65,13 @@ spec:
name: api-config
key: JWT_SECRET
livenessProbe:
- tcpSocket:
- port: 6000
+ httpGet:
+ path: /actuator/health
+ port: 8080
+ httpHeaders:
+ - name: Health-Check
+ value: health-check
initialDelaySeconds: 60
- periodSeconds: 10
- failureThreshold: 3
- name: ngrok
command:
- /bin/bash
diff --git a/infrastructure/helm-chart/charts/apps/charts/sources-facebook-events-router/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources-facebook-events-router/templates/deployment.yaml
index 4e7356cdef..4430f199ed 100644
--- a/infrastructure/helm-chart/charts/apps/charts/sources-facebook-events-router/templates/deployment.yaml
+++ b/infrastructure/helm-chart/charts/apps/charts/sources-facebook-events-router/templates/deployment.yaml
@@ -6,6 +6,7 @@ metadata:
labels:
app: sources-facebook-events-router
type: sources-facebook
+ core.airy.co/managed: "true"
spec:
replicas: 0
selector:
diff --git a/infrastructure/helm-chart/charts/apps/charts/sources-google-connector/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources-google-connector/templates/deployment.yaml
index 9681372125..c92caaee2a 100644
--- a/infrastructure/helm-chart/charts/apps/charts/sources-google-connector/templates/deployment.yaml
+++ b/infrastructure/helm-chart/charts/apps/charts/sources-google-connector/templates/deployment.yaml
@@ -6,6 +6,7 @@ metadata:
labels:
app: sources-google-connector
type: sources-google
+ core.airy.co/managed: "true"
spec:
replicas: 0
selector:
@@ -57,11 +58,13 @@ spec:
name: api-config
key: JWT_SECRET
livenessProbe:
- tcpSocket:
- port: 6000
+ httpGet:
+ path: /actuator/health
+ port: 8080
+ httpHeaders:
+ - name: Health-Check
+ value: health-check
initialDelaySeconds: 60
- periodSeconds: 10
- failureThreshold: 3
- name: ngrok
command:
- /bin/bash
diff --git a/infrastructure/helm-chart/charts/apps/charts/sources-google-events-router/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources-google-events-router/templates/deployment.yaml
index bf366ca3c3..94360c1d7d 100644
--- a/infrastructure/helm-chart/charts/apps/charts/sources-google-events-router/templates/deployment.yaml
+++ b/infrastructure/helm-chart/charts/apps/charts/sources-google-events-router/templates/deployment.yaml
@@ -6,6 +6,7 @@ metadata:
labels:
app: sources-google-events-router
type: sources-google
+ core.airy.co/managed: "true"
spec:
replicas: 0
selector:
@@ -41,6 +42,16 @@ spec:
configMapKeyRef:
name: kafka-config
key: KAFKA_COMMIT_INTERVAL_MS
+ - name: GOOGLE_SA_FILE
+ valueFrom:
+ configMapKeyRef:
+ name: sources-google
+ key: GOOGLE_SA_FILE
+ - name: GOOGLE_PARTNER_KEY
+ valueFrom:
+ configMapKeyRef:
+ name: sources-google
+ key: GOOGLE_PARTNER_KEY
livenessProbe:
tcpSocket:
port: 6000
diff --git a/infrastructure/helm-chart/charts/apps/charts/sources-twilio-connector/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources-twilio-connector/templates/deployment.yaml
index 807a4fa113..dfddbfa752 100644
--- a/infrastructure/helm-chart/charts/apps/charts/sources-twilio-connector/templates/deployment.yaml
+++ b/infrastructure/helm-chart/charts/apps/charts/sources-twilio-connector/templates/deployment.yaml
@@ -6,6 +6,7 @@ metadata:
labels:
app: sources-twilio-connector
type: sources-twilio
+ core.airy.co/managed: "true"
spec:
replicas: 0
selector:
@@ -57,11 +58,13 @@ spec:
name: api-config
key: JWT_SECRET
livenessProbe:
- tcpSocket:
- port: 6000
+ httpGet:
+ path: /actuator/health
+ port: 8080
+ httpHeaders:
+ - name: Health-Check
+ value: health-check
initialDelaySeconds: 60
- periodSeconds: 10
- failureThreshold: 3
- name: ngrok
command:
- /bin/bash
diff --git a/infrastructure/helm-chart/charts/apps/charts/sources-twilio-events-router/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources-twilio-events-router/templates/deployment.yaml
index ad099f29c4..0e4f3be848 100644
--- a/infrastructure/helm-chart/charts/apps/charts/sources-twilio-events-router/templates/deployment.yaml
+++ b/infrastructure/helm-chart/charts/apps/charts/sources-twilio-events-router/templates/deployment.yaml
@@ -6,6 +6,7 @@ metadata:
labels:
app: sources-twilio-events-router
type: sources-twilio
+ core.airy.co/managed: "true"
spec:
replicas: 0
selector:
@@ -41,6 +42,16 @@ spec:
configMapKeyRef:
name: kafka-config
key: KAFKA_COMMIT_INTERVAL_MS
+ - name: TWILIO_AUTH_TOKEN
+ valueFrom:
+ configMapKeyRef:
+ name: sources-twilio
+ key: TWILIO_AUTH_TOKEN
+ - name: TWILIO_ACCOUNT_SID
+ valueFrom:
+ configMapKeyRef:
+ name: sources-twilio
+ key: TWILIO_ACCOUNT_SID
livenessProbe:
tcpSocket:
port: 6000
diff --git a/infrastructure/helm-chart/charts/apps/charts/webhook-consumer/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/webhook-consumer/templates/deployment.yaml
index 53a468057b..ccc6ddc098 100644
--- a/infrastructure/helm-chart/charts/apps/charts/webhook-consumer/templates/deployment.yaml
+++ b/infrastructure/helm-chart/charts/apps/charts/webhook-consumer/templates/deployment.yaml
@@ -6,6 +6,7 @@ metadata:
labels:
app: webhook-consumer
type: webhook
+ core.airy.co/managed: "true"
spec:
replicas: 0
selector:
@@ -53,6 +54,11 @@ spec:
configMapKeyRef:
name: redis-config
key: REDIS_PORT
+ - name: WEBHOOK_NAME
+ valueFrom:
+ configMapKeyRef:
+ name: webhooks-config
+ key: NAME
livenessProbe:
httpGet:
path: /health
diff --git a/infrastructure/helm-chart/charts/apps/charts/webhook-publisher/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/webhook-publisher/templates/deployment.yaml
index d19f5e3069..f32870f51e 100644
--- a/infrastructure/helm-chart/charts/apps/charts/webhook-publisher/templates/deployment.yaml
+++ b/infrastructure/helm-chart/charts/apps/charts/webhook-publisher/templates/deployment.yaml
@@ -6,6 +6,7 @@ metadata:
labels:
app: webhook-publisher
type: webhook
+ core.airy.co/managed: "true"
spec:
replicas: 0
selector:
@@ -53,6 +54,11 @@ spec:
configMapKeyRef:
name: redis-config
key: REDIS_PORT
+ - name: WEBHOOK_NAME
+ valueFrom:
+ configMapKeyRef:
+ name: webhooks-config
+ key: NAME
livenessProbe:
tcpSocket:
port: 6000
diff --git a/infrastructure/helm-chart/charts/controller/templates/deployment.yaml b/infrastructure/helm-chart/charts/controller/templates/deployment.yaml
index cbceda4924..1b7d39f419 100644
--- a/infrastructure/helm-chart/charts/controller/templates/deployment.yaml
+++ b/infrastructure/helm-chart/charts/controller/templates/deployment.yaml
@@ -26,3 +26,6 @@ spec:
- name: app
image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}"
imagePullPolicy: Always
+ env:
+ - name: LABEL_SELECTOR
+ value: "core.airy.co/managed=true"
diff --git a/infrastructure/helm-chart/charts/ingress/Chart.yaml b/infrastructure/helm-chart/charts/ingress/Chart.yaml
new file mode 100644
index 0000000000..01e96eb5fc
--- /dev/null
+++ b/infrastructure/helm-chart/charts/ingress/Chart.yaml
@@ -0,0 +1,5 @@
+apiVersion: v1
+appVersion: "1.0"
+description: A Helm chart for the Traefik ingress controller
+name: ingress
+version: 0.1.0
diff --git a/infrastructure/helm-chart/charts/ingress/templates/configmap.yaml b/infrastructure/helm-chart/charts/ingress/templates/configmap.yaml
new file mode 100644
index 0000000000..297ad1c5b9
--- /dev/null
+++ b/infrastructure/helm-chart/charts/ingress/templates/configmap.yaml
@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: hostnames
+ namespace: {{ .Values.global.namespace }}
+data:
+ API_HOST: {{ .Values.apiHost }}
+ UI_HOST: {{ .Values.uiHost }}
+ CHATPLUGIN_HOST: {{ .Values.chatpluginHost }}
diff --git a/infrastructure/network/ingress.yaml b/infrastructure/helm-chart/charts/ingress/templates/ingress.yaml
similarity index 95%
rename from infrastructure/network/ingress.yaml
rename to infrastructure/helm-chart/charts/ingress/templates/ingress.yaml
index bce0ce57c2..d3b25ac926 100644
--- a/infrastructure/network/ingress.yaml
+++ b/infrastructure/helm-chart/charts/ingress/templates/ingress.yaml
@@ -2,9 +2,10 @@ kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: 'airy-core'
+ namespace: {{ .Values.global.namespace }}
spec:
rules:
- - host: 'api.airy'
+ - host: {{ .Values.apiHost }}
http:
paths:
- path: /users.login
@@ -77,6 +78,13 @@ spec:
name: api-communication
port:
number: 80
+ - path: /client.config
+ pathType: Prefix
+ backend:
+ service:
+ name: api-admin
+ port:
+ number: 80
- path: /channels.list
pathType: Prefix
backend:
@@ -210,7 +218,7 @@ spec:
name: sources-twilio-connector
port:
number: 80
- - host: 'demo.airy'
+ - host: {{ .Values.uiHost }}
http:
paths:
- path: /
@@ -220,7 +228,7 @@ spec:
name: frontend-demo
port:
number: 80
- - host: 'chatplugin.airy'
+ - host: {{ .Values.chatpluginHost }}
http:
paths:
- path: /ws.chatplugin
diff --git a/infrastructure/helm-chart/charts/ingress/values.yaml b/infrastructure/helm-chart/charts/ingress/values.yaml
new file mode 100644
index 0000000000..7dce025b09
--- /dev/null
+++ b/infrastructure/helm-chart/charts/ingress/values.yaml
@@ -0,0 +1,4 @@
+
+apiHost: "api.airy"
+uiHost: "demo.airy"
+chatpluginHost: "chatplugin.airy"
diff --git a/infrastructure/helm-chart/charts/kafka/charts/kafka/values.yaml b/infrastructure/helm-chart/charts/kafka/charts/kafka/values.yaml
index 9cd848c8af..5a0fc937ab 100644
--- a/infrastructure/helm-chart/charts/kafka/charts/kafka/values.yaml
+++ b/infrastructure/helm-chart/charts/kafka/charts/kafka/values.yaml
@@ -1,6 +1,6 @@
brokers: 1
image: ghcr.io/airyhq/infrastructure/kafka
-imageTag: release
+imageTag: 2.5.1
imagePullPolicy: IfNotPresent
imagePullSecrets:
podManagementPolicy: OrderedReady
diff --git a/infrastructure/helm-chart/charts/kafka/charts/schema-registry/templates/deployment.yaml b/infrastructure/helm-chart/charts/kafka/charts/schema-registry/templates/deployment.yaml
index c4763f40a5..a8dd878e4f 100644
--- a/infrastructure/helm-chart/charts/kafka/charts/schema-registry/templates/deployment.yaml
+++ b/infrastructure/helm-chart/charts/kafka/charts/schema-registry/templates/deployment.yaml
@@ -24,7 +24,7 @@ spec:
spec:
containers:
- name: schema-registry-server
- image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}"
+ image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: "{{ .Values.imagePullPolicy }}"
ports:
- name: schema-registry
diff --git a/infrastructure/helm-chart/charts/kafka/charts/schema-registry/values.yaml b/infrastructure/helm-chart/charts/kafka/charts/schema-registry/values.yaml
index 1ed01b5c09..b76d2b791e 100644
--- a/infrastructure/helm-chart/charts/kafka/charts/schema-registry/values.yaml
+++ b/infrastructure/helm-chart/charts/kafka/charts/schema-registry/values.yaml
@@ -1,5 +1,6 @@
replicaCount: 0
image: infrastructure/schema-registry
+imageTag: 2.0.1
imagePullPolicy: Always
servicePort: 8081
kafka:
diff --git a/infrastructure/helm-chart/charts/kafka/charts/zookeeper/values.yaml b/infrastructure/helm-chart/charts/kafka/charts/zookeeper/values.yaml
index 97a2ff75fc..e93c0527a1 100644
--- a/infrastructure/helm-chart/charts/kafka/charts/zookeeper/values.yaml
+++ b/infrastructure/helm-chart/charts/kafka/charts/zookeeper/values.yaml
@@ -1,6 +1,6 @@
servers: 1
image: ghcr.io/airyhq/infrastructure/kafka
-imageTag: release
+imageTag: 2.5.1
imagePullPolicy: IfNotPresent
imagePullSecrets:
podManagementPolicy: OrderedReady
diff --git a/infrastructure/images/kafka/Makefile b/infrastructure/images/kafka/Makefile
index 716c33ca55..10d19433a8 100644
--- a/infrastructure/images/kafka/Makefile
+++ b/infrastructure/images/kafka/Makefile
@@ -1,10 +1,6 @@
build:
docker build -t airy-kafka .
-release-beta: build
- docker tag airy-kafka ghcr.io/airyhq/infrastructure/kafka:beta
- docker push ghcr.io/airyhq/infrastructure/kafka:beta
-
release: build
- docker tag airy-kafka ghcr.io/airyhq/infrastructure/kafka:release
- docker push ghcr.io/airyhq/infrastructure/kafka:release
+ docker tag airy-kafka ghcr.io/airyhq/infrastructure/kafka:2.5.1
+ docker push ghcr.io/airyhq/infrastructure/kafka:2.5.1
diff --git a/infrastructure/images/nginx/Dockerfile b/infrastructure/images/nginx/Dockerfile
new file mode 100644
index 0000000000..c3870d3e11
--- /dev/null
+++ b/infrastructure/images/nginx/Dockerfile
@@ -0,0 +1,5 @@
+FROM fabiocicerchia/nginx-lua:1.19.6-alpine3.13.0
+
+RUN apk add --no-cache lua5.1-dev luarocks5.1
+RUN luarocks-5.1 install lua-resty-template
+RUN apk del lua5.1-dev luarocks5.1
diff --git a/infrastructure/images/nginx/Makefile b/infrastructure/images/nginx/Makefile
new file mode 100644
index 0000000000..91419b5941
--- /dev/null
+++ b/infrastructure/images/nginx/Makefile
@@ -0,0 +1,6 @@
+build:
+ docker build -t nginx-lua .
+
+release: build
+ docker tag nginx-lua ghcr.io/airyhq/frontend/nginx-lua:1.0.0
+ docker push ghcr.io/airyhq/frontend/nginx-lua:1.0.0
diff --git a/infrastructure/images/schema-registry/Makefile b/infrastructure/images/schema-registry/Makefile
index d1aa464411..529ac7e112 100644
--- a/infrastructure/images/schema-registry/Makefile
+++ b/infrastructure/images/schema-registry/Makefile
@@ -1,10 +1,7 @@
build:
docker build -t schema-registry . --build-arg SCHEMA_REGISTRY_VERSION=${SCHEMA_REGISTRY_VERSION}
-release-beta: build
- docker tag schema-registry ghcr.io/airyhq/infrastructure/schema-registry:beta
- docker push ghcr.io/airyhq/infrastructure/schema-registry:beta
-
release: build
- docker tag schema-registry ghcr.io/airyhq/infrastructure/schema-registry:release
- docker push ghcr.io/airyhq/infrastructure/schema-registry:release
+ docker tag schema-registry ghcr.io/airyhq/infrastructure/schema-registry:2.0.1
+ docker push ghcr.io/airyhq/infrastructure/schema-registry:2.0.1
+
diff --git a/infrastructure/images/tasks/pull-images.sh b/infrastructure/images/tasks/pull-images.sh
deleted file mode 100755
index a0bb68b5ab..0000000000
--- a/infrastructure/images/tasks/pull-images.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-set -euo pipefail
-IFS=$'\n\t'
-
-docker pull confluentinc/cp-kafka:5.5.0
-docker pull confluentinc/cp-zookeeper:5.5.0
-docker pull postgres:12.4-alpine
-docker pull redis:5.0.1-alpine
diff --git a/infrastructure/images/vagrant.json b/infrastructure/images/vagrant.json
index da2cdf66b2..17f54e1fb4 100644
--- a/infrastructure/images/vagrant.json
+++ b/infrastructure/images/vagrant.json
@@ -16,12 +16,6 @@
}
],
"provisioners": [
- {
- "scripts": [
- "tasks/pull-images.sh"
- ],
- "type": "shell"
- },
{
"scripts": [
"../scripts/provision.sh"
@@ -30,7 +24,7 @@
},
{
"scripts": [
- "../scripts/airy-conf.sh"
+ "../scripts/airy.yaml.sh"
],
"type": "shell"
}
diff --git a/infrastructure/lib/go/k8s/handler/BUILD b/infrastructure/lib/go/k8s/handler/BUILD
index 14f813ff08..ba971cf2d2 100644
--- a/infrastructure/lib/go/k8s/handler/BUILD
+++ b/infrastructure/lib/go/k8s/handler/BUILD
@@ -1,5 +1,4 @@
-# gazelle:prefix handler
-# gazelle:prefix k8s.io/kubernetes
+# gazelle:prefix github.com/airyhq/airy/infrastructure/lib/go/k8s/handler
load("@io_bazel_rules_go//go:def.bzl", "go_library")
diff --git a/infrastructure/lib/go/k8s/handler/configmaps.go b/infrastructure/lib/go/k8s/handler/configmaps.go
index a58f7d0273..4e47c519d3 100644
--- a/infrastructure/lib/go/k8s/handler/configmaps.go
+++ b/infrastructure/lib/go/k8s/handler/configmaps.go
@@ -1,17 +1,17 @@
package handler
import (
- "github.com/airyhq/airy/infrastructure/lib/go/k8s/util"
-
+ "context"
v1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
)
-// GetConfigmapConfig provides utility config for configmap
-func GetConfigmapConfig(configmap *v1.ConfigMap) Config {
- return Config{
- Namespace: configmap.Namespace,
- Name: configmap.Name,
- SHAValue: util.GetSHAfromConfigmap(configmap),
- Type: "CONFIGMAP",
- }
+func ConfigMapExists(name string, clientSet kubernetes.Interface, namespace string) bool {
+ configMap, err := GetConfigMap(name, clientSet, namespace)
+ return configMap != nil && err == nil
+}
+
+func GetConfigMap(name string, clientSet kubernetes.Interface, namespace string) (*v1.ConfigMap, error) {
+ return clientSet.CoreV1().ConfigMaps(namespace).Get(context.TODO(), name, metav1.GetOptions{})
}
diff --git a/infrastructure/lib/go/k8s/handler/containers.go b/infrastructure/lib/go/k8s/handler/containers.go
index 6e22022ff7..662a0656b9 100644
--- a/infrastructure/lib/go/k8s/handler/containers.go
+++ b/infrastructure/lib/go/k8s/handler/containers.go
@@ -74,3 +74,26 @@ func getContainerWithEnvReference(containers []v1.Container, resourceName string
}
return nil
}
+
+// Does not return config maps whose only use is marked "optional"
+func GetReferencedConfigMaps(container v1.Container) []string {
+ envs := container.Env
+ var configMaps []string
+ for j := range envs {
+ envVarSource := envs[j].ValueFrom
+ if envVarSource != nil && envVarSource.ConfigMapKeyRef != nil &&
+ (envVarSource.ConfigMapKeyRef.Optional == nil || *envVarSource.ConfigMapKeyRef.Optional != true) {
+ configMaps = append(configMaps, envVarSource.ConfigMapKeyRef.LocalObjectReference.Name)
+ }
+ }
+
+ envsFrom := container.EnvFrom
+ for j := range envsFrom {
+ if envsFrom[j].ConfigMapRef != nil &&
+ (envsFrom[j].ConfigMapRef.Optional == nil || *envsFrom[j].ConfigMapRef.Optional != true) {
+ configMaps = append(configMaps, envsFrom[j].ConfigMapRef.LocalObjectReference.Name)
+ }
+ }
+
+ return configMaps
+}
diff --git a/infrastructure/lib/go/k8s/handler/deployments.go b/infrastructure/lib/go/k8s/handler/deployments.go
index ad86ebab9b..2790e29a52 100644
--- a/infrastructure/lib/go/k8s/handler/deployments.go
+++ b/infrastructure/lib/go/k8s/handler/deployments.go
@@ -2,38 +2,22 @@ package handler
import (
"context"
+ apps_v1 "k8s.io/api/apps/v1"
"github.com/airyhq/airy/infrastructure/lib/go/k8s/util"
v1 "k8s.io/api/core/v1"
- meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/util/retry"
"k8s.io/klog"
)
-func GetDeployments(clientset kubernetes.Interface, namespace string, labelSelector string) ([]string, error) {
- var deployments []string
- deploymentsClient := clientset.AppsV1().Deployments(namespace)
+func GetDeploymentsReferencingCm(clientSet kubernetes.Interface, configMapName string, namespace string, labelSelector string) ([]apps_v1.Deployment, error) {
+ deploymentsClient := clientSet.AppsV1().Deployments(namespace)
+ var deployments []apps_v1.Deployment
retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
- result, getErr := deploymentsClient.List(context.TODO(), meta_v1.ListOptions{LabelSelector: labelSelector})
- if getErr != nil {
- klog.Errorf("Failed to get latest version of the Deployments: %v", getErr)
- return getErr
- }
- for _, deploymentItem := range (*result).Items {
- deployments = append(deployments, deploymentItem.Name)
- }
- return nil
- })
- return deployments, retryErr
-}
-
-func GetAffectedDeploymentsConfigmap(clientset kubernetes.Interface, configmapName string, namespace string, labelSelector string) ([]string, error) {
- deploymentsClient := clientset.AppsV1().Deployments(namespace)
- var affectedDeployments []string
- retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
- result, getErr := deploymentsClient.List(context.TODO(), meta_v1.ListOptions{LabelSelector: labelSelector})
+ result, getErr := deploymentsClient.List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector})
if getErr != nil {
klog.Errorf("Failed to get latest version of the Deployments: %v", getErr)
return getErr
@@ -44,64 +28,107 @@ func GetAffectedDeploymentsConfigmap(clientset kubernetes.Interface, configmapNa
initContainers := GetDeploymentInitContainers(deploymentItem)
// Check the containers which have an EnvReference
- container = getContainerWithEnvReference(containers, configmapName, "CONFIGMAP")
+ container = getContainerWithEnvReference(containers, configMapName, ConfigmapEnvVarPostfix)
if container != nil {
klog.Infof("Found affected container in deployment: %s", deploymentItem.Name)
- affectedDeployments = append(affectedDeployments, deploymentItem.Name)
+ deployments = append(deployments, deploymentItem)
} else {
- container = getContainerWithEnvReference(initContainers, configmapName, "CONFIGMAP")
+ container = getContainerWithEnvReference(initContainers, configMapName, ConfigmapEnvVarPostfix)
if container != nil {
klog.Infof("Found affected initContainer in deployment: %s", deploymentItem.Name)
- affectedDeployments = append(affectedDeployments, deploymentItem.Name)
+ deployments = append(deployments, deploymentItem)
}
}
// Check the containers which have a VolumeMount
volumes := GetDeploymentVolumes(deploymentItem)
- volumeMountName := getVolumeMountName(volumes, "CONFIGMAP", configmapName)
+ volumeMountName := getVolumeMountName(volumes, ConfigmapEnvVarPostfix, configMapName)
if volumeMountName != "" {
container = getContainerWithVolumeMount(containers, volumeMountName)
if container == nil && len(initContainers) > 0 {
container = getContainerWithVolumeMount(initContainers, volumeMountName)
if container != nil {
// if configmap/secret is being used in init container then return the first Pod container to save reloader env
- affectedDeployments = append(affectedDeployments, deploymentItem.Name)
+ deployments = append(deployments, deploymentItem)
}
} else if container != nil {
- affectedDeployments = append(affectedDeployments, deploymentItem.Name)
+ deployments = append(deployments, deploymentItem)
}
}
}
return nil
})
- return affectedDeployments, retryErr
+ return deployments, retryErr
}
-func ReloadDeployment(clientset kubernetes.Interface, namespace string, deploymentName string) error {
- deploymentsClient := clientset.AppsV1().Deployments(namespace)
- deployment, getErr := deploymentsClient.Get(context.TODO(), deploymentName, meta_v1.GetOptions{})
+// Won't do anything for replicas that are scaled down
+func ReloadDeployment(deployment apps_v1.Deployment, clientSet kubernetes.Interface) error {
+ deploymentsClient := clientSet.AppsV1().Deployments(deployment.Namespace)
+
currentReplicas := deployment.Spec.Replicas
- // If currentReplicas is 0 - don't do anything
- if *currentReplicas != 0 {
- retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
- deployment, getErr = deploymentsClient.Get(context.TODO(), deploymentName, meta_v1.GetOptions{})
- if getErr != nil {
- klog.Errorf("Failed to get latest version of Deployment: %v", getErr)
- return getErr
+ if *currentReplicas == 0 {
+ return nil
+ }
+
+ return retry.RetryOnConflict(retry.DefaultRetry, func() error {
+ deployment, err := deploymentsClient.Get(context.TODO(), deployment.Name, metav1.GetOptions{})
+ if err != nil {
+ klog.Errorf("Failed to get latest version of Deployment: %v", err)
+ return err
+ }
+ deployment.Spec.Replicas = util.Int32Ptr(0) // reduce replica count
+ _, updateErr := deploymentsClient.Update(context.TODO(), deployment, metav1.UpdateOptions{})
+ deployment.Spec.Replicas = currentReplicas // increase replica count
+ _, updateErr = deploymentsClient.Update(context.TODO(), deployment, metav1.UpdateOptions{})
+ return updateErr
+ })
+}
+
+type ScaleCommand struct {
+ ClientSet kubernetes.Interface
+ Namespace string
+ DeploymentName string
+ DesiredReplicas int32
+}
+
+func ScaleDeployment(command ScaleCommand) error {
+ deploymentsClient := command.ClientSet.AppsV1().Deployments(command.Namespace)
+ return retry.RetryOnConflict(retry.DefaultRetry, func() error {
+ deployment, err := deploymentsClient.Get(context.TODO(), command.DeploymentName, metav1.GetOptions{})
+ if err != nil {
+ klog.Errorf("failed to get latest version of Deployment: %v", err)
+ return err
+ }
+
+ if *deployment.Spec.Replicas == command.DesiredReplicas {
+ return nil
+ }
+
+ deployment.Spec.Replicas = util.Int32Ptr(command.DesiredReplicas)
+ _, updateErr := deploymentsClient.Update(context.TODO(), deployment, metav1.UpdateOptions{})
+ return updateErr
+ })
+}
+
+func CanBeStarted(deployment apps_v1.Deployment, clientSet kubernetes.Interface) bool {
+ containers := GetDeploymentContainers(deployment)
+
+ // Check that all referenced configMaps are present
+ checkedConfigMaps := make(map[string]bool)
+ for _, container := range containers {
+ configMaps := GetReferencedConfigMaps(container)
+
+ for _, configMapName := range configMaps {
+ if !checkedConfigMaps[configMapName] {
+ if !ConfigMapExists(configMapName, clientSet, deployment.Namespace) {
+ return false
+ }
+ checkedConfigMaps[configMapName] = true
}
- deployment.Spec.Replicas = util.Int32Ptr(0) // reduce replica count
- _, updateErr := deploymentsClient.Update(context.TODO(), deployment, meta_v1.UpdateOptions{})
- deployment.Spec.Replicas = currentReplicas // increase replica count
- _, updateErr = deploymentsClient.Update(context.TODO(), deployment, meta_v1.UpdateOptions{})
- return updateErr
- })
- if retryErr != nil {
- klog.Errorf("Update failed: %v", retryErr)
- return retryErr
}
- klog.Infof("Reloaded deployment %s", deploymentName)
- return nil
}
- return getErr
+
+ return true
}
+
diff --git a/infrastructure/lib/go/k8s/util/BUILD b/infrastructure/lib/go/k8s/util/BUILD
index c6387d371e..f7106742fc 100644
--- a/infrastructure/lib/go/k8s/util/BUILD
+++ b/infrastructure/lib/go/k8s/util/BUILD
@@ -1,5 +1,4 @@
-# gazelle:prefix util
-# gazelle:prefix k8s.io/kubernetes
+# gazelle:prefix github.com/airyhq/airy/infrastructure/lib/go/k8s/util
load("@io_bazel_rules_go//go:def.bzl", "go_library")
diff --git a/infrastructure/scripts/conf.sh b/infrastructure/scripts/conf.sh
deleted file mode 100755
index 59b00b0f68..0000000000
--- a/infrastructure/scripts/conf.sh
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/bin/bash
-set -eo pipefail
-IFS=$'\n\t'
-
-SCRIPT_PATH=$(cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P)
-INFRASTRUCTURE_PATH=$(cd ${SCRIPT_PATH}/../; pwd -P)
-
-if [[ ! -f ${INFRASTRUCTURE_PATH}/airy.conf ]]; then
- echo "No airy.conf config file found"
- exit 0
-fi
-
-source ${INFRASTRUCTURE_PATH}/scripts/lib/k8s.sh
-
-DEPLOYED_AIRY_VERSION=`kubectl get configmap core-config -o jsonpath='{.data.APP_IMAGE_TAG}'`
-if $(grep -q " appImageTag" ${INFRASTRUCTURE_PATH}/airy.conf); then
- CONFIGURED_AIRY_VERSION=$(grep " appImageTag" ${INFRASTRUCTURE_PATH}/airy.conf | head -n 1 | awk '{ print $2}')
-else
- CONFIGURED_AIRY_VERSION=""
-fi
-
-if [ -z ${CONFIGURED_AIRY_VERSION} ]; then
- AIRY_VERSION=${DEPLOYED_AIRY_VERSION}
-else
- AIRY_VERSION=${CONFIGURED_AIRY_VERSION}
-fi
-
-kubectl delete pod startup-helper --force 2>/dev/null || true
-kubectl run startup-helper --image busybox --command -- /bin/sh -c "tail -f /dev/null"
-
-helm upgrade core ${INFRASTRUCTURE_PATH}/helm-chart/ --values ${INFRASTRUCTURE_PATH}/airy.conf --set global.appImageTag=${AIRY_VERSION} --timeout 1000s > /dev/null 2>&1
-
-kubectl scale deployment schema-registry --replicas=1
-
-wait-for-running-pod startup-helper
-wait-for-service startup-helper schema-registry 8081 15 "Schema registry"
-
-kubectl scale deployment -l type=api --replicas=1
-kubectl scale deployment -l type=sources-chatplugin --replicas=1
-kubectl scale deployment -l type=frontend --replicas=1
-
-wait-for-service startup-helper api-auth 80 10 api-auth
-
-kubectl scale deployment -l type=sources-twilio --replicas=1
-kubectl scale deployment -l type=sources-google --replicas=1
-kubectl scale deployment -l type=sources-facebook --replicas=1
-kubectl scale deployment -l type=webhook --replicas=1
-
-kubectl delete pod startup-helper --force 2>/dev/null
diff --git a/infrastructure/scripts/provision/core.sh b/infrastructure/scripts/provision/core.sh
index 3e99c62ee6..6a64859f18 100755
--- a/infrastructure/scripts/provision/core.sh
+++ b/infrastructure/scripts/provision/core.sh
@@ -2,18 +2,31 @@
set -euo pipefail
IFS=$'\n\t'
+AIRY_VERSION=${AIRY_VERSION}
SCRIPT_PATH=$(cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P)
INFRASTRUCTURE_PATH=$(cd ${SCRIPT_PATH}/../../; pwd -P)
source ${INFRASTRUCTURE_PATH}/scripts/lib/k8s.sh
-APP_IMAGE_TAG="${AIRY_VERSION:-latest}"
-echo "Deploying the Airy Core Platform with the ${APP_IMAGE_TAG} image tag"
cd ${INFRASTRUCTURE_PATH}/scripts/
wait-for-service-account
-helm install core ${INFRASTRUCTURE_PATH}/helm-chart/ --set global.appImageTag=${APP_IMAGE_TAG} --version 0.5.0 --timeout 1000s > /dev/null 2>&1
+
+echo "Deploying the Airy Core Platform with the ${AIRY_VERSION} image tag"
+
+if [[ -f ${INFRASTRUCTURE_PATH}/airy.yaml ]]; then
+ yq eval '.global.appImageTag="'${AIRY_VERSION}'"' -i ${INFRASTRUCTURE_PATH}/airy.yaml
+ helm install core ${INFRASTRUCTURE_PATH}/helm-chart/ --values ${INFRASTRUCTURE_PATH}/airy.yaml --timeout 1000s > /dev/null 2>&1
+ wget -qnv https://airy-core-binaries.s3.amazonaws.com/alpine/airy.gz
+ gunzip airy.gz
+ chmod +x airy
+ mv airy /usr/local/bin/
+ airy init
+ airy config apply --kube-config /etc/rancher/k3s/k3s.yaml --config ${INFRASTRUCTURE_PATH}/airy.yaml
+else
+ helm install core ${INFRASTRUCTURE_PATH}/helm-chart/ --set global.appImageTag=${AIRY_VERSION} --timeout 1000s > /dev/null 2>&1
+fi
kubectl run startup-helper --image busybox --command -- /bin/sh -c "tail -f /dev/null"
@@ -29,6 +42,3 @@ wait-for-service startup-helper postgres 5432 10 Postgres
kubectl scale statefulset redis-cluster --replicas=1
wait-for-service startup-helper redis-cluster 6379 10 Redis
kubectl delete pod startup-helper --force 2>/dev/null
-
-echo "Deploying ingress controller"
-kubectl apply -f ../network/ingress.yaml
diff --git a/infrastructure/scripts/provision/prerequisites.sh b/infrastructure/scripts/provision/prerequisites.sh
index 16d16d04d2..544b31dc17 100755
--- a/infrastructure/scripts/provision/prerequisites.sh
+++ b/infrastructure/scripts/provision/prerequisites.sh
@@ -2,7 +2,9 @@
set -euo pipefail
IFS=$'\n\t'
-apk add --no-cache wget unzip jq bash-completion
+echo "http://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories
+apk update
+apk add --no-cache wget unzip jq yq bash-completion
curl -sfL https://get.k3s.io | sh -
diff --git a/infrastructure/scripts/status.sh b/infrastructure/scripts/status.sh
index 062259b724..7b743dd9a9 100755
--- a/infrastructure/scripts/status.sh
+++ b/infrastructure/scripts/status.sh
@@ -14,7 +14,8 @@ wait-for-ingress-service
wait-for-running-pod startup-helper
wait-for-service startup-helper api-auth 80 10 api-auth
-CORE_ID=`kubectl get configmap core-config -o jsonpath='{.data.CORE_ID}'`
+CORE_ID=$(kubectl get configmap core-config -o jsonpath='{.data.CORE_ID}')
+API_HOSTNAME=$(kubectl get ingress airy-core -o jsonpath='{.spec.rules[0].host}')
FACEBOOK_WEBHOOK_PUBLIC_URL="https://fb-${CORE_ID}.tunnel.airy.co"
GOOGLE_WEBHOOK_PUBLIC_URL="https://gl-${CORE_ID}.tunnel.airy.co"
TWILIO_WEBHOOK_PUBLIC_URL="https://tw-${CORE_ID}.tunnel.airy.co"
@@ -30,9 +31,9 @@ echo "Your public url for the Twilio Webhook is:"
echo ${TWILIO_WEBHOOK_PUBLIC_URL}/twilio
echo
echo "You can access the API of the Airy Core Platform at:"
-echo "http://api.airy/"
+echo "http://${API_HOSTNAME}"
echo
echo "Example:"
-echo "curl -X POST -H 'Content-Type: application/json' -d '{\"first_name\": \"Grace\",\"last_name\": \"Hopper\",\"password\": \"the_answer_is_42\",\"email\": \"grace@example.com\"}' http://api.airy/users.signup"
+echo "curl -X POST -H 'Content-Type: application/json' -d '{\"first_name\": \"Grace\",\"last_name\": \"Hopper\",\"password\": \"the_answer_is_42\",\"email\": \"grace@example.com\"}' http://${API_HOSTNAME}/users.signup"
kubectl delete pod startup-helper --force 2>/dev/null
diff --git a/infrastructure/scripts/trigger/start.sh b/infrastructure/scripts/trigger/start.sh
index 7956bad5f0..fa715e4447 100755
--- a/infrastructure/scripts/trigger/start.sh
+++ b/infrastructure/scripts/trigger/start.sh
@@ -24,15 +24,10 @@ wait-for-service startup-helper schema-registry 8081 15 "Schema registry"
echo "Starting up Airy Core Platform appplications"
kubectl scale deployment -l type=api --replicas=1
-kubectl scale deployment -l type=sources-chatplugin --replicas=1
-kubectl scale deployment -l type=frontend --replicas=1
wait-for-service startup-helper api-auth 80 10 api-auth
-kubectl scale deployment -l type=sources-twilio --replicas=1
-kubectl scale deployment -l type=sources-google --replicas=1
-kubectl scale deployment -l type=sources-facebook --replicas=1
-kubectl scale deployment -l type=webhook --replicas=1
+kubectl scale deployment -l app=airy-controller --replicas=1
kubectl delete pod startup-helper --force 2>/dev/null
chmod o+r /etc/rancher/k3s/k3s.yaml
diff --git a/infrastructure/scripts/trigger/stop.sh b/infrastructure/scripts/trigger/stop.sh
index cbe03d2f9c..587f8bd514 100755
--- a/infrastructure/scripts/trigger/stop.sh
+++ b/infrastructure/scripts/trigger/stop.sh
@@ -3,12 +3,14 @@ set -euo pipefail
IFS=$'\n\t'
echo "Scaling down Airy Core platform applications"
+kubectl scale deployment -l app=airy-controller --replicas=0
kubectl scale deployment -l type=frontend --replicas=0
kubectl scale deployment -l type=sources-twilio --replicas=0
kubectl scale deployment -l type=sources-google --replicas=0
kubectl scale deployment -l type=sources-facebook --replicas=0
kubectl scale deployment -l type=sources-chatplugin --replicas=0
kubectl scale deployment -l type=webhook --replicas=0
+kubectl scale deployment -l type=media --replicas=0
kubectl scale deployment -l type=sources-chatplugin --replicas=0
kubectl scale deployment -l type=api --replicas=0
kubectl scale deployment schema-registry --replicas=0
diff --git a/lib/go/httpclient/BUILD b/lib/go/httpclient/BUILD
index 01b9934a9b..778fda8778 100644
--- a/lib/go/httpclient/BUILD
+++ b/lib/go/httpclient/BUILD
@@ -1,10 +1,11 @@
-# gazelle:prefix httpclient
-# gazelle:importmap_prefix lib/go
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+# gazelle:prefix github.com/airyhq/airy/lib/go/httpclient
go_library(
name = "httpclient",
srcs = [
+ "client.go",
"httpclient.go",
"users.go",
],
@@ -12,3 +13,13 @@ go_library(
visibility = ["//visibility:public"],
deps = ["//lib/go/httpclient/payloads"],
)
+
+go_test(
+ name = "httpclient_test",
+ srcs = ["users_test.go"],
+ embed = [":httpclient"],
+ deps = [
+ "//lib/go/httpclient/payloads",
+ "@com_github_stretchr_testify//assert",
+ ],
+)
diff --git a/lib/go/httpclient/client.go b/lib/go/httpclient/client.go
new file mode 100644
index 0000000000..681cd4dc11
--- /dev/null
+++ b/lib/go/httpclient/client.go
@@ -0,0 +1,23 @@
+package httpclient
+
+import (
+ "encoding/json"
+
+ "github.com/airyhq/airy/lib/go/httpclient/payloads"
+)
+
+func (c *Client) Config() (*payloads.ClientConfigResponsePayload, error) {
+ payload, err := json.Marshal(payloads.ClientConfigRequestPayload{})
+ if err != nil {
+ return nil, err
+ }
+
+ res := payloads.ClientConfigResponsePayload{}
+
+ e := c.post("client.config", payload, &res)
+ if e != nil {
+ return nil, e
+ }
+
+ return &res, nil
+}
diff --git a/lib/go/httpclient/http-client-test/BUILD b/lib/go/httpclient/http-client-test/BUILD
deleted file mode 100644
index 3a4a8ebe15..0000000000
--- a/lib/go/httpclient/http-client-test/BUILD
+++ /dev/null
@@ -1,11 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-go_test(
- name = "http-client-test_test",
- srcs = ["users_test.go"],
- deps = [
- "//lib/go/httpclient",
- "//lib/go/httpclient/payloads",
- "@com_github_stretchr_testify//assert",
- ],
-)
diff --git a/lib/go/httpclient/httpclient.go b/lib/go/httpclient/httpclient.go
index f557d556a9..8d214ca43c 100644
--- a/lib/go/httpclient/httpclient.go
+++ b/lib/go/httpclient/httpclient.go
@@ -3,63 +3,48 @@ package httpclient
import (
"bytes"
"encoding/json"
- "errors"
"fmt"
"net/http"
"time"
)
-const (
- BaseURL = "http://api.airy"
-)
-
type Client struct {
- BaseURL string
- HTTPClient *http.Client
+ BaseURL string
+ JWTToken string
+ c *http.Client
}
-func NewClient() *Client {
+func NewClient(baseURL string) *Client {
return &Client{
- BaseURL: BaseURL,
- HTTPClient: &http.Client{
+ BaseURL: baseURL,
+ c: &http.Client{
Timeout: time.Minute,
},
}
}
-type errorResponse struct {
- Code int `json:"code"`
- Message string `json:"message"`
-}
-
-func (c *Client) sendRequest(requestDataJSON []byte, endpoint string, v interface{}) error {
-
- req, err := http.NewRequest("POST", fmt.Sprintf("%s/%s", c.BaseURL, endpoint), bytes.NewBuffer(requestDataJSON))
+func (c *Client) post(endpoint string, payload []byte, res interface{}) error {
+ req, err := http.NewRequest("POST", fmt.Sprintf("%s/%s", c.BaseURL, endpoint), bytes.NewBuffer(payload))
if err != nil {
return err
}
+
req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.Header.Set("Accept", "application/json; charset=utf-8")
+ if c.JWTToken != "" {
+ req.Header.Set("Authorization", c.JWTToken)
+ }
- res, err := c.HTTPClient.Do(req)
+ r, err := c.c.Do(req)
if err != nil {
return err
}
- defer res.Body.Close()
-
- if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
- var errRes errorResponse
- if err = json.NewDecoder(res.Body).Decode(&errRes); err == nil {
- return errors.New(errRes.Message)
- }
+ defer r.Body.Close()
- return fmt.Errorf("unknown error, status code: %d", res.StatusCode)
- }
-
- if err = json.NewDecoder(res.Body).Decode(v); err != nil {
- return err
+ if r.StatusCode < http.StatusOK || r.StatusCode >= http.StatusBadRequest {
+ return fmt.Errorf("request was unsuccessful. Status code: %d", r.StatusCode)
}
- return nil
+ return json.NewDecoder(r.Body).Decode(&res)
}
diff --git a/lib/go/httpclient/payloads/BUILD b/lib/go/httpclient/payloads/BUILD
index e313270567..33775dcaf9 100644
--- a/lib/go/httpclient/payloads/BUILD
+++ b/lib/go/httpclient/payloads/BUILD
@@ -1,12 +1,15 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
+# gazelle:prefix github.com/airyhq/airy/lib/go/httpclient/payloads
go_library(
name = "payloads",
srcs = [
- "login_request.go",
- "login_response.go",
- "signup_request.go",
- "signup_response.go",
+ "client_config_request_payload.go",
+ "client_config_response_payload.go",
+ "login_request_payload.go",
+ "login_response_payload.go",
+ "signup_request_payload.go",
+ "signup_response_payload.go",
],
importpath = "github.com/airyhq/airy/lib/go/httpclient/payloads",
visibility = ["//visibility:public"],
diff --git a/lib/go/httpclient/payloads/client_config_request_payload.go b/lib/go/httpclient/payloads/client_config_request_payload.go
new file mode 100644
index 0000000000..f132cbb455
--- /dev/null
+++ b/lib/go/httpclient/payloads/client_config_request_payload.go
@@ -0,0 +1,4 @@
+package payloads
+
+type ClientConfigRequestPayload struct {
+}
diff --git a/lib/go/httpclient/payloads/client_config_response_payload.go b/lib/go/httpclient/payloads/client_config_response_payload.go
new file mode 100644
index 0000000000..23ab1d77a6
--- /dev/null
+++ b/lib/go/httpclient/payloads/client_config_response_payload.go
@@ -0,0 +1,6 @@
+package payloads
+
+type ClientConfigResponsePayload struct {
+ Components map[string]map[string]interface{} `json:"components"`
+ Features map[string]string `json:"features"`
+}
diff --git a/lib/go/httpclient/payloads/login_request.go b/lib/go/httpclient/payloads/login_request_payload.go
similarity index 100%
rename from lib/go/httpclient/payloads/login_request.go
rename to lib/go/httpclient/payloads/login_request_payload.go
diff --git a/lib/go/httpclient/payloads/login_response.go b/lib/go/httpclient/payloads/login_response_payload.go
similarity index 100%
rename from lib/go/httpclient/payloads/login_response.go
rename to lib/go/httpclient/payloads/login_response_payload.go
diff --git a/lib/go/httpclient/payloads/signup_request.go b/lib/go/httpclient/payloads/signup_request_payload.go
similarity index 100%
rename from lib/go/httpclient/payloads/signup_request.go
rename to lib/go/httpclient/payloads/signup_request_payload.go
diff --git a/lib/go/httpclient/payloads/signup_response.go b/lib/go/httpclient/payloads/signup_response_payload.go
similarity index 100%
rename from lib/go/httpclient/payloads/signup_response.go
rename to lib/go/httpclient/payloads/signup_response_payload.go
diff --git a/lib/go/httpclient/users.go b/lib/go/httpclient/users.go
index 9c858be202..8a5af50f6f 100644
--- a/lib/go/httpclient/users.go
+++ b/lib/go/httpclient/users.go
@@ -7,31 +7,30 @@ import (
)
func (c *Client) Signup(signupRequestPayload payloads.SignupRequestPayload) (*payloads.SignupResponsePayload, error) {
- requestDataJSON, err := json.Marshal(signupRequestPayload)
+ payload, err := json.Marshal(signupRequestPayload)
if err != nil {
return nil, err
}
+
res := payloads.SignupResponsePayload{}
- if err := c.sendRequest(requestDataJSON, "users.signup", &res); err != nil {
- return nil, err
+ e := c.post("users.signup", payload, &res)
+ if e != nil {
+ return nil, e
}
return &res, nil
-
}
func (c *Client) Login(loginRequestPayload payloads.LoginRequestPayload) (*payloads.LoginResponsePayload, error) {
- requestDataJSON, err := json.Marshal(loginRequestPayload)
+ payload, err := json.Marshal(loginRequestPayload)
if err != nil {
return nil, err
}
res := payloads.LoginResponsePayload{}
-
- if err := c.sendRequest(requestDataJSON, "users.login", &res); err != nil {
- return nil, err
+ e := c.post("users.login", payload, &res)
+ if e != nil {
+ return nil, e
}
-
return &res, nil
-
}
diff --git a/lib/go/httpclient/http-client-test/users_test.go b/lib/go/httpclient/users_test.go
similarity index 55%
rename from lib/go/httpclient/http-client-test/users_test.go
rename to lib/go/httpclient/users_test.go
index e2cb5ea19f..8124dab466 100644
--- a/lib/go/httpclient/http-client-test/users_test.go
+++ b/lib/go/httpclient/users_test.go
@@ -1,4 +1,4 @@
-package tests
+package httpclient
import (
"fmt"
@@ -6,19 +6,20 @@ import (
"net/http/httptest"
"testing"
- "github.com/airyhq/airy/lib/go/httpclient"
"github.com/airyhq/airy/lib/go/httpclient/payloads"
-
"github.com/stretchr/testify/assert"
)
-func TestSignup(t *testing.T) {
- c := httpclient.NewClient()
-
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+func mockedUserResponseServer() *httptest.Server {
+ return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "{\"id\":\"a6c413a7-8d42-4c2b-8736-d033134eec59\",\"first_name\":\"Grace\",\"last_name\":\"Hopper\",\"token\":\"eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJzdWIiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJpYXQiOjE2MDczMzY2NjMsInVzZXJfaWQiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJleHAiOjE2MDc0MjMwNjN9.I4sf2j36RQCPRrirzSYyRhJ4U3bG2sUmHfxX4yBJvQA\"}")
}))
- c.BaseURL = ts.URL
+}
+
+func TestSignup(t *testing.T) {
+ ts := mockedUserResponseServer()
+ defer ts.Close()
+ c := NewClient(ts.URL)
signupRequestPayload := payloads.SignupRequestPayload{FirstName: "Grace", LastName: "Hopper", Password: "the_answer_is_42", Email: "grace@example.com"}
@@ -26,26 +27,18 @@ func TestSignup(t *testing.T) {
assert.Nil(t, err, "expecting nil error")
assert.NotNil(t, res, "expecting non-nil result")
-
- assert.NotEmpty(t, res.Token, "expecting non-empty token")
-
+ assert.Equal(t, res.Token, "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJzdWIiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJpYXQiOjE2MDczMzY2NjMsInVzZXJfaWQiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJleHAiOjE2MDc0MjMwNjN9.I4sf2j36RQCPRrirzSYyRhJ4U3bG2sUmHfxX4yBJvQA")
}
func TestLogin(t *testing.T) {
- c := httpclient.NewClient()
-
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintln(w, "{\"id\":\"a6c413a7-8d42-4c2b-8736-d033134eec59\",\"first_name\":\"Grace\",\"last_name\":\"Hopper\",\"token\":\"eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJzdWIiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJpYXQiOjE2MDczMzY2NjMsInVzZXJfaWQiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJleHAiOjE2MDc0MjMwNjN9.I4sf2j36RQCPRrirzSYyRhJ4U3bG2sUmHfxX4yBJvQA\"}")
- }))
- c.BaseURL = ts.URL
+ ts := mockedUserResponseServer()
+ defer ts.Close()
+ c := NewClient(ts.URL)
loginRequestPayload := payloads.LoginRequestPayload{Password: "the_answer_is_42", Email: "grace@example.com"}
res, err := c.Login(loginRequestPayload)
-
assert.Nil(t, err, "expecting nil error")
assert.NotNil(t, res, "expecting non-nil result")
-
- assert.NotEmpty(t, res.Token, "expecting non-empty token")
-
+ assert.Equal(t, res.Token, "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJzdWIiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJpYXQiOjE2MDczMzY2NjMsInVzZXJfaWQiOiJhNmM0MTNhNy04ZDQyLTRjMmItODczNi1kMDMzMTM0ZWVjNTkiLCJleHAiOjE2MDc0MjMwNjN9.I4sf2j36RQCPRrirzSYyRhJ4U3bG2sUmHfxX4yBJvQA")
}
diff --git a/lib/java/mapping/src/main/java/co/airy/mapping/OutboundMapper.java b/lib/java/mapping/src/main/java/co/airy/mapping/OutboundMapper.java
index ac3eb07efd..a0b16dcf21 100644
--- a/lib/java/mapping/src/main/java/co/airy/mapping/OutboundMapper.java
+++ b/lib/java/mapping/src/main/java/co/airy/mapping/OutboundMapper.java
@@ -18,7 +18,7 @@ public OutboundMapper() {
}
public List render(String payload) throws Exception {
- final JsonNode jsonNode = objectMapper.readTree(payload);
- return List.of(new Text(jsonNode.get("text").textValue()));
+ final Content content = objectMapper.readValue(payload, Content.class);
+ return List.of(content);
}
}
diff --git a/lib/java/mapping/src/main/java/co/airy/mapping/model/Content.java b/lib/java/mapping/src/main/java/co/airy/mapping/model/Content.java
index ffb4681b99..bdcd8534f2 100644
--- a/lib/java/mapping/src/main/java/co/airy/mapping/model/Content.java
+++ b/lib/java/mapping/src/main/java/co/airy/mapping/model/Content.java
@@ -9,7 +9,8 @@
@JsonSubTypes.Type(value = Audio.class, name = "audio"),
@JsonSubTypes.Type(value = File.class, name = "file"),
@JsonSubTypes.Type(value = Image.class, name = "image"),
- @JsonSubTypes.Type(value = Video.class, name = "video")
+ @JsonSubTypes.Type(value = Video.class, name = "video"),
+ @JsonSubTypes.Type(value = SourceTemplate.class, name = "source.template")
})
public abstract class Content {
}
diff --git a/lib/java/mapping/src/main/java/co/airy/mapping/model/SourceTemplate.java b/lib/java/mapping/src/main/java/co/airy/mapping/model/SourceTemplate.java
new file mode 100644
index 0000000000..84456cdc02
--- /dev/null
+++ b/lib/java/mapping/src/main/java/co/airy/mapping/model/SourceTemplate.java
@@ -0,0 +1,21 @@
+package co.airy.mapping.model;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = false)
+public class SourceTemplate extends Content implements Serializable {
+ @NotNull
+ private JsonNode payload;
+}
diff --git a/lib/java/mapping/src/main/java/co/airy/mapping/sources/facebook/FacebookMapper.java b/lib/java/mapping/src/main/java/co/airy/mapping/sources/facebook/FacebookMapper.java
index 0c8c10fb0b..85705ae58b 100644
--- a/lib/java/mapping/src/main/java/co/airy/mapping/sources/facebook/FacebookMapper.java
+++ b/lib/java/mapping/src/main/java/co/airy/mapping/sources/facebook/FacebookMapper.java
@@ -5,6 +5,7 @@
import co.airy.mapping.model.Content;
import co.airy.mapping.model.File;
import co.airy.mapping.model.Image;
+import co.airy.mapping.model.SourceTemplate;
import co.airy.mapping.model.Text;
import co.airy.mapping.model.Video;
import com.fasterxml.jackson.databind.JsonNode;
@@ -31,6 +32,7 @@ public FacebookMapper() {
"audio", Audio::new,
"file", File::new
);
+
@Override
public List getIdentifiers() {
return List.of("facebook");
@@ -52,6 +54,12 @@ public List render(String payload) throws Exception {
.elements()
.forEachRemaining(attachmentNode -> {
final String attachmentType = attachmentNode.get("type").textValue();
+
+ if (attachmentType.equals("template")) {
+ contents.add(new SourceTemplate(attachmentNode.get("payload")));
+ return;
+ }
+
final String url = attachmentNode.get("payload").get("url").textValue();
final Content mediaContent = mediaContentFactory.get(attachmentType).apply(url);
diff --git a/lib/java/mapping/src/main/java/co/airy/mapping/sources/google/GoogleMapper.java b/lib/java/mapping/src/main/java/co/airy/mapping/sources/google/GoogleMapper.java
index 0b252c8233..6ce335b784 100644
--- a/lib/java/mapping/src/main/java/co/airy/mapping/sources/google/GoogleMapper.java
+++ b/lib/java/mapping/src/main/java/co/airy/mapping/sources/google/GoogleMapper.java
@@ -32,7 +32,18 @@ public List getIdentifiers() {
public List render(String payload) throws Exception {
final JsonNode jsonNode = objectMapper.readTree(payload);
final JsonNode messageNode = jsonNode.get("message");
+ if (messageNode != null) {
+ return renderMessage(messageNode);
+ }
+ final JsonNode suggestionResponseNode = jsonNode.get("suggestionResponse");
+ if (suggestionResponseNode != null) {
+ return renderSuggestionResponse(suggestionResponseNode);
+ }
+
+ throw new Exception("google mapper only supports `message` and `suggestionResponse`");
+ }
+ private List renderMessage(JsonNode messageNode) {
final String messageNodeValue = messageNode.get("text").textValue();
if (isGoogleStorageUrl(messageNodeValue)) {
return List.of(new Image(messageNodeValue));
@@ -41,6 +52,11 @@ public List render(String payload) throws Exception {
}
}
+ private List renderSuggestionResponse(JsonNode suggestionResponseNode) {
+ final String textContent = suggestionResponseNode.get("text").textValue();
+ return List.of(new Text(textContent));
+ }
+
private boolean isGoogleStorageUrl(final String url) {
URI uri;
try {
diff --git a/lib/java/mapping/src/test/java/co/airy/mapping/ContentMapperTest.java b/lib/java/mapping/src/test/java/co/airy/mapping/ContentMapperTest.java
index c2bc10c2d6..57cb265b93 100644
--- a/lib/java/mapping/src/test/java/co/airy/mapping/ContentMapperTest.java
+++ b/lib/java/mapping/src/test/java/co/airy/mapping/ContentMapperTest.java
@@ -35,7 +35,9 @@ public class ContentMapperTest {
@Test
void rendersOutbound() throws Exception {
- final String text = "Hello World";
+ final String textContent = "Hello World";
+ final Text text = new Text(textContent);
+
final Message message = Message.newBuilder()
.setId("other-message-id")
.setSource("facebook")
@@ -45,12 +47,12 @@ void rendersOutbound() throws Exception {
.setDeliveryState(DeliveryState.DELIVERED)
.setConversationId("conversationId")
.setChannelId("channelId")
- .setContent("{\"text\":\"" + text + "\"}")
+ .setContent((new ObjectMapper()).writeValueAsString(text))
.build();
final Text textMessage = (Text) mapper.render(message).get(0);
- assertThat(textMessage.getText(), equalTo(text));
+ assertThat(textMessage.getText(), equalTo(textContent));
Mockito.verify(outboundMapper).render(Mockito.anyString());
}
@@ -102,7 +104,6 @@ public List render(String payload) {
final String persistentUrl = "http://storage.org/path/data";
final Map messageMetadata = Map.of("data_" + originalUrl, persistentUrl);
- // No replacement without metadata
audioMessage = (Audio) mapper.render(message, messageMetadata).get(0);
assertThat(audioMessage.getUrl(), equalTo(persistentUrl));
}
diff --git a/lib/java/mapping/src/test/java/co/airy/mapping/FacebookTest.java b/lib/java/mapping/src/test/java/co/airy/mapping/FacebookTest.java
index 5f20f56751..8a3fcb9335 100644
--- a/lib/java/mapping/src/test/java/co/airy/mapping/FacebookTest.java
+++ b/lib/java/mapping/src/test/java/co/airy/mapping/FacebookTest.java
@@ -3,13 +3,16 @@
import co.airy.mapping.model.Audio;
import co.airy.mapping.model.Content;
import co.airy.mapping.model.File;
+import co.airy.mapping.model.SourceTemplate;
import co.airy.mapping.model.Video;
import co.airy.mapping.sources.facebook.FacebookMapper;
import org.junit.jupiter.api.Test;
import org.springframework.util.StreamUtils;
+import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
+import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@@ -74,4 +77,16 @@ void canRenderFile() throws Exception {
assertThat(contents, everyItem(isA(File.class)));
assertThat(contents, everyItem(hasProperty("url", equalTo(fileUrl))));
}
+
+ @Test
+ void canRenderTemplates() throws Exception {
+ final List templateTypes = List.of("generic");
+
+ for (String templateType : templateTypes) {
+ final String content = StreamUtils.copyToString(getClass().getClassLoader().getResourceAsStream(String.format("facebook/template_%s.json", templateType)), StandardCharsets.UTF_8);
+ final List contents = mapper.render(content);
+ assertThat(contents, hasSize(1));
+ assertThat(contents, everyItem(isA(SourceTemplate.class)));
+ }
+ }
}
diff --git a/lib/java/mapping/src/test/java/co/airy/mapping/GoogleTest.java b/lib/java/mapping/src/test/java/co/airy/mapping/GoogleTest.java
index 2a11d8e72c..f33159bbaa 100644
--- a/lib/java/mapping/src/test/java/co/airy/mapping/GoogleTest.java
+++ b/lib/java/mapping/src/test/java/co/airy/mapping/GoogleTest.java
@@ -4,6 +4,9 @@
import co.airy.mapping.model.Text;
import co.airy.mapping.sources.google.GoogleMapper;
import org.junit.jupiter.api.Test;
+import org.springframework.util.StreamUtils;
+
+import java.nio.charset.StandardCharsets;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@@ -13,48 +16,31 @@ public class GoogleTest {
@Test
void canRenderText() throws Exception {
- final String content = "{\n" +
- " \"message\": {\n" +
- " \"name\": \"conversations/9cec28cc-8dbe-40d0-ad68-edd0f440c743/messages/3A25E132-20D6-4A5D-8602-7DF4979F181B\",\n" +
- " \"text\": \"Yes confirmed\",\n" +
- " \"createTime\": \"2020-05-14T12:45:54.531828Z\",\n" +
- " \"messageId\": \"3A25E132-20D6-4A5D-8602-7DF4979F181B\"\n" +
- " },\n" +
- " \"context\": {},\n" +
- " \"sendTime\": \"2020-05-14T12:45:55.302Z\",\n" +
- " \"conversationId\": \"9cec28cc-8dbe-40d0-ad68-edd0f440c743\",\n" +
- " \"customAgentId\": \"5b43b04d-aa75-4b7b-bdca-28e90a344db1\",\n" +
- " \"requestId\": \"3A25E132-20D6-4A5D-8602-7DF4979F181B\",\n" +
- " \"agent\": \"brands/af0ef816-cef8-479e-b4b6-650d5e8b90b1/agents/31a8d3e0-490f-4ecc-887b-42df4dd1952e\"\n" +
- "}";
-
- final Text message = (Text) mapper.render(content).get(0);
- assertThat(message.getText(), equalTo("Yes confirmed"));
+ final String textContent = "Hello World";
+ final String sourceContent = String.format(StreamUtils.copyToString(getClass().getClassLoader()
+ .getResourceAsStream("google/text.json"), StandardCharsets.UTF_8), textContent);
+
+ final Text message = (Text) mapper.render(sourceContent).get(0);
+ assertThat(message.getText(), equalTo(textContent));
}
@Test
void canRenderImage() throws Exception {
final String signedImageUrl = "https://storage.googleapis.com/business-messages-us/936640919331/jzsu6cdguNGsBhmGJGuLs1DS?x-goog-algorithm\u003dGOOG4-RSA-SHA256\u0026x-goog-credential\u003duranium%40rcs-uranium.iam.gserviceaccount.com%2F20190826%2Fauto%2Fstorage%2Fgoog4_request\u0026x-goog-date\u003d20190826T201038Z\u0026x-goog-expires\u003d604800\u0026x-goog-signedheaders\u003dhost\u0026x-goog-signature\u003d89dbf7a74d21ab42ad25be071b37840a544a43d68e67270382054e1442d375b0b53d15496dbba12896b9d88a6501cac03b5cfca45d789da3e0cae75b050a89d8f54c1ffb27e467bd6ba1d146b7d42e30504c295c5c372a46e44728f554ba74b7b99bd9c6d3ed45f18588ed1b04522af1a47330cff73a711a6a8c65bb15e3289f480486f6695127e1014727cac949e284a7f74afd8220840159c589d48dddef1cc97b248dfc34802570448242eac4d7190b1b10a008404a330b4ff6f9656fa84e87f9a18ab59dc9b91e54ad11ffdc0ad1dc9d1ccc7855c0d263d93fce6f999971ec79879f922b582cf3bb196a1fedc3eefa226bb412e49af7dfd91cc072608e98";
- final String content = "{\n" +
- " \"agent\": \"brands/BRAND_ID/agents/AGENT_ID\",\n" +
- " \"conversationId\": \"CONVERSATION_ID\",\n" +
- " \"customAgentId\": \"CUSTOM_AGENT_ID\",\n" +
- " \"requestId\": \"REQUEST_ID\",\n" +
- " \"message\": {\n" +
- " \"messageId\": \"MESSAGE_ID\",\n" +
- " \"name\": \"conversations/CONVERSATION_ID/messages/MESSAGE_ID\",\n" +
- " \"text\": \"" + signedImageUrl + "\",\n" +
- " \"createTime\": \"MESSAGE_CREATE_TIME\"\n" +
- " },\n" +
- " \"context\": {},\n" +
- " \"sendTime\": \"2020-05-14T12:45:55.302Z\",\n" +
- " \"conversationId\": \"9cec28cc-8dbe-40d0-ad68-edd0f440c743\",\n" +
- " \"customAgentId\": \"5b43b04d-aa75-4b7b-bdca-28e90a344db1\",\n" +
- " \"requestId\": \"3A25E132-20D6-4A5D-8602-7DF4979F181B\",\n" +
- " \"agent\": \"brands/af0ef816-cef8-479e-b4b6-650d5e8b90b1/agents/31a8d3e0-490f-4ecc-887b-42df4dd1952e\"\n" +
- "}\n";
-
- final Image message = (Image) mapper.render(content).get(0);
+ final String sourceContent = String.format(StreamUtils.copyToString(getClass().getClassLoader()
+ .getResourceAsStream("google/text.json"), StandardCharsets.UTF_8), signedImageUrl);
+
+ final Image message = (Image) mapper.render(sourceContent).get(0);
assertThat(message.getUrl(), equalTo(signedImageUrl));
}
+
+ @Test
+ void canRenderSuggestionResponses() throws Exception {
+ final String textContent = "Hello World";
+ final String sourceContent = String.format(StreamUtils.copyToString(getClass().getClassLoader()
+ .getResourceAsStream("google/suggestionResponse.json"), StandardCharsets.UTF_8), textContent);
+
+ final Text message = (Text) mapper.render(sourceContent).get(0);
+ assertThat(message.getText(), equalTo(textContent));
+ }
}
diff --git a/lib/java/mapping/src/test/resources/facebook/template_generic.json b/lib/java/mapping/src/test/resources/facebook/template_generic.json
new file mode 100644
index 0000000000..9012127a66
--- /dev/null
+++ b/lib/java/mapping/src/test/resources/facebook/template_generic.json
@@ -0,0 +1,33 @@
+{
+ "sender": {
+ "id": "4616529495039079"
+ },
+ "recipient": {
+ "id": "778234505682382"
+ },
+ "timestamp": 1550050473934,
+ "message": {
+ "is_echo": true,
+ "app_id": 123,
+ "mid": "l9sIeXHGFbkAL5m62DbqF2nKw8PPGcoZ0ruggAoYBrnyu8w-rcnEazyvqHqp3VeTu8k3NK-N1fCnAdzcs9kcUw",
+ "seq": 85083,
+ "attachments": [
+ {
+ "title": "test",
+ "url": null,
+ "type": "template",
+ "payload": {
+ "template_type": "generic",
+ "sharable": true,
+ "elements": [
+ {
+ "title": "awdadw",
+ "image_url": "https://airy-layer-production.s3.amazonaws.com/templates/8787c530-2f72-11e9-867e-f7de52fd949f.jpeg",
+ "subtitle": "adww"
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
diff --git a/lib/java/mapping/src/test/resources/google/suggestionResponse.json b/lib/java/mapping/src/test/resources/google/suggestionResponse.json
new file mode 100644
index 0000000000..5b6f1110a1
--- /dev/null
+++ b/lib/java/mapping/src/test/resources/google/suggestionResponse.json
@@ -0,0 +1,15 @@
+{
+ "suggestionResponse": {
+ "message": "conversations/11ab7bf3-0410-46a6-bcce-1877ca6957b0/messages/11ab7bf3-0410-46a6-bcce-1877ca6957b0",
+ "postbackData": "postback-data",
+ "createTime": "2020-07-13T12:54:50.479632Z",
+ "text": "%s",
+ "type": "REPLY"
+ },
+ "context": {},
+ "sendTime": "2020-05-14T12:45:55.302Z",
+ "conversationId": "11ab7bf3-0410-46a6-bcce-1877ca6957b0",
+ "customAgentId": "11ab7bf3-0410-46a6-bcce-1877ca6957b0",
+ "requestId": "11ab7bf3-0410-46a6-bcce-1877ca6957b0",
+ "agent": "brands/11ab7bf3-0410-46a6-bcce-1877ca6957b0/agents/11ab7bf3-0410-46a6-bcce-1877ca6957b0"
+}
diff --git a/lib/java/mapping/src/test/resources/google/text.json b/lib/java/mapping/src/test/resources/google/text.json
new file mode 100644
index 0000000000..fa2156c57f
--- /dev/null
+++ b/lib/java/mapping/src/test/resources/google/text.json
@@ -0,0 +1,14 @@
+{
+ "message": {
+ "name": "conversations/11ab7bf3-0410-46a6-bcce-1877ca6957b0/messages/11ab7bf3-0410-46a6-bcce-1877ca6957b0",
+ "text": "%s",
+ "createTime": "2020-05-14T12:45:54.531828Z",
+ "messageId": "11ab7bf3-0410-46a6-bcce-1877ca6957b0"
+ },
+ "context": {},
+ "sendTime": "2020-05-14T12:45:55.302Z",
+ "conversationId": "11ab7bf3-0410-46a6-bcce-1877ca6957b0",
+ "customAgentId": "11ab7bf3-0410-46a6-bcce-1877ca6957b0",
+ "requestId": "11ab7bf3-0410-46a6-bcce-1877ca6957b0",
+ "agent": "brands/11ab7bf3-0410-46a6-bcce-1877ca6957b0/agents/11ab7bf3-0410-46a6-bcce-1877ca6957b0"
+}
diff --git a/lib/java/mapping/src/ts-generator/java/co/airy/ts_generator/Main.java b/lib/java/mapping/src/ts-generator/java/co/airy/ts_generator/Main.java
index a56e9d5f0b..e4b7c8ed51 100644
--- a/lib/java/mapping/src/ts-generator/java/co/airy/ts_generator/Main.java
+++ b/lib/java/mapping/src/ts-generator/java/co/airy/ts_generator/Main.java
@@ -27,7 +27,7 @@ public static void main(String[] args) {
parameters.debug = false;
parameters.classNamePatterns = List.of("co.airy.mapping.model.**");
- final File output = new File(System.getenv().get("BUILD_WORKSPACE_DIRECTORY") + "/frontend/types/content.ts");
+ final File output = new File(System.getenv().get("BUILD_WORKSPACE_DIRECTORY") + "/lib/typescript/types/content.ts");
settings.validateFileName(output);
generator.generateTypeScript(Input.from(parameters), Output.to(output));
diff --git a/lib/java/url/BUILD b/lib/java/url/BUILD
new file mode 100644
index 0000000000..e16ada072e
--- /dev/null
+++ b/lib/java/url/BUILD
@@ -0,0 +1,7 @@
+load("//tools/build:java_library.bzl", "custom_java_library")
+
+custom_java_library(
+ name = "url",
+ srcs = glob(["src/main/java/co/airy/url/**/*.java"]),
+ visibility = ["//visibility:public"],
+)
diff --git a/lib/java/url/src/main/java/co/airy/url/UrlUtil.java b/lib/java/url/src/main/java/co/airy/url/UrlUtil.java
new file mode 100644
index 0000000000..cfdfaadacf
--- /dev/null
+++ b/lib/java/url/src/main/java/co/airy/url/UrlUtil.java
@@ -0,0 +1,29 @@
+package co.airy.url;
+
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.stream.Collectors.toMap;
+
+public class UrlUtil {
+ public static Map parseUrlEncoded(String payload) {
+ List kvPairs = Arrays.asList(payload.split("&"));
+
+ return kvPairs.stream()
+ .map((kvPair) -> {
+ String[] fields = kvPair.split("=");
+
+ String name = URLDecoder.decode(fields[0], StandardCharsets.UTF_8);
+ String value = "";
+ if (fields.length > 1) {
+ value = URLDecoder.decode(fields[1], StandardCharsets.UTF_8);
+ }
+
+ return List.of(name, value);
+ })
+ .collect(toMap((tuple) -> tuple.get(0), (tuple) -> tuple.get(1)));
+ }
+}
diff --git a/lib/typescript/httpclient/README.md b/lib/typescript/httpclient/README.md
index b90afc4eb7..44d4f4c88a 100644
--- a/lib/typescript/httpclient/README.md
+++ b/lib/typescript/httpclient/README.md
@@ -1,23 +1,22 @@
### HttpClient Library
-The HttpClient Library includes helper functions for using Airy's endpoints on the frontend.
-
-Each function performs an http request and returns a promise.
-
-To use the library's functions, import the library and call the module's methods.
+The HttpClient Library includes a HTTP client for making requests to Airy's API.
+The library exports a HttpClient class. To use the library, you need to instantiate the class with the authentification token and your api url. Both the authentification token and api url are optional (the default api url is "http://api.airy"), but communication with the endpoints always requires a token, except for /users.login and /users.signup endpoints.
For example:
```
import { HttpClient} from 'httpclient';
-HttpClient.listChannels()
+const myInstance = new HttpClient(authtoken, apiUrl);
+
+myInstance.listChannels()
```
-Here is a list of the functions it includes:
+Here is a list of the public methods the library's class includes:
CHANNELS
- listChannels
@@ -27,6 +26,10 @@ CHANNELS
CONVERSATIONS
- listConversations
+- readConversations
+
+MESSAGES
+- listMessages
TAGS
- listTags
diff --git a/lib/typescript/httpclient/api/airyConfig.ts b/lib/typescript/httpclient/api/airyConfig.ts
deleted file mode 100644
index d3b76d00c6..0000000000
--- a/lib/typescript/httpclient/api/airyConfig.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import {getAuthToken} from './webStore';
-
-export class AiryConfig {
- static API_URL = 'http://api.airy';
- static NODE_ENV = process.env.NODE_ENV;
- static FACEBOOK_APP_ID = 'CHANGE_ME';
-}
-
-const headers = {
- Accept: 'application/json',
-};
-
-export const doFetchFromBackend = async (url: string, body?: Object): Promise => {
- const token = getAuthToken();
- if (token) {
- headers['Authorization'] = token;
- }
-
- if (!(body instanceof FormData)) {
- if (!isString(body)) {
- body = JSON.stringify(body);
- }
- headers['Content-Type'] = 'application/json';
- }
-
- const response: Response = await fetch(`${AiryConfig.API_URL}/${url}`, {
- method: 'POST',
- headers: headers,
- body: body as BodyInit,
- });
-
- return parseBody(response);
-};
-
-async function parseBody(response: Response): Promise {
- if (response.ok) {
- return response.json();
- }
-
- let body = await response.text();
- if (body.length > 0) {
- body = JSON.parse(body);
- }
-
- const errorResponse = {
- status: response.status,
- body: body,
- };
-
- throw errorResponse;
-}
-
-function isString(object: any) {
- return typeof object === 'string' || object instanceof String;
-}
diff --git a/lib/typescript/httpclient/endpoints/connectChannel.ts b/lib/typescript/httpclient/endpoints/connectChannel.ts
deleted file mode 100644
index 768f182985..0000000000
--- a/lib/typescript/httpclient/endpoints/connectChannel.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import {doFetchFromBackend} from '../api';
-import {ConnectChannelRequestPayload, ConnectChannelRequestApiPayload} from '../payload';
-import {Channel} from '../model';
-import {ChannelApiPayload} from '../payload/ChannelApiPayload';
-
-const connectChannelApiMapper = (payload: ConnectChannelRequestPayload): ConnectChannelRequestApiPayload => {
- return {
- source: payload.source,
- source_channel_id: payload.sourceChannelId,
- token: payload.token,
- name: payload.name,
- image_url: payload.imageUrl,
- };
-};
-
-const channelMapper = (payload: ChannelApiPayload): Channel => {
- return {
- name: payload.name,
- source: payload.source,
- sourceChannelId: payload.source_channel_id,
- imageUrl: payload.image_url,
- connected: true,
- };
-};
-
-export function connectChannel(requestPayload: ConnectChannelRequestPayload) {
- return doFetchFromBackend('channels.connect', connectChannelApiMapper(requestPayload))
- .then((response: ChannelApiPayload) => {
- const channel = channelMapper(response);
- return channel;
- })
- .catch((error: Error) => {
- return error;
- });
-}
diff --git a/lib/typescript/httpclient/endpoints/createTag.ts b/lib/typescript/httpclient/endpoints/createTag.ts
deleted file mode 100644
index 2313504601..0000000000
--- a/lib/typescript/httpclient/endpoints/createTag.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import {doFetchFromBackend} from '../api';
-import {Tag, TagColor} from '../model';
-import {CreateTagRequestPayload} from '../payload';
-import {TagPayload} from '../payload/TagPayload';
-
-export function createTag(requestPayload: CreateTagRequestPayload) {
- return doFetchFromBackend('tags.create', requestPayload)
- .then((response: TagPayload) => {
- const tag: Tag = {
- id: response.id,
- name: requestPayload.name,
- color: requestPayload.color as TagColor,
- };
- return tag;
- })
- .catch((error: Error) => {
- return error;
- });
-}
diff --git a/lib/typescript/httpclient/endpoints/deleteTag.ts b/lib/typescript/httpclient/endpoints/deleteTag.ts
deleted file mode 100644
index 72abdf31d9..0000000000
--- a/lib/typescript/httpclient/endpoints/deleteTag.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import {doFetchFromBackend} from '../api';
-
-export function deleteTag(id: string) {
- return doFetchFromBackend('tags.delete', {
- id,
- })
- .then(() => Promise.resolve(true))
- .catch((error: Error) => Promise.reject(error));
-}
diff --git a/lib/typescript/httpclient/endpoints/disconnectChannel.ts b/lib/typescript/httpclient/endpoints/disconnectChannel.ts
deleted file mode 100644
index c63d5ece66..0000000000
--- a/lib/typescript/httpclient/endpoints/disconnectChannel.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import {doFetchFromBackend} from '../api';
-import {DisconnectChannelRequestPayload, DisconnectChannelRequestApiPayload} from '../payload';
-import {Channel} from '../model';
-import {ChannelsPayload} from '../payload/ChannelsPayload';
-
-const channelsMapper = (payload: ChannelsPayload, source?: string): Channel[] => {
- return payload.data.map(
- (entry: Channel): Channel => {
- return {
- source,
- ...entry,
- };
- }
- );
-};
-
-const disconnectChannelApiMapper = (payload: DisconnectChannelRequestPayload): DisconnectChannelRequestApiPayload => {
- return {
- channel_id: payload.channelId,
- };
-};
-
-export function disconnectChannel(requestPayload: DisconnectChannelRequestPayload) {
- return doFetchFromBackend('channels.disconnect', disconnectChannelApiMapper(requestPayload))
- .then((response: ChannelsPayload) => {
- const channels = channelsMapper(response);
- return channels;
- })
- .catch((error: Error) => {
- return error;
- });
-}
diff --git a/lib/typescript/httpclient/endpoints/exploreChannels.ts b/lib/typescript/httpclient/endpoints/exploreChannels.ts
deleted file mode 100644
index 6de5235e61..0000000000
--- a/lib/typescript/httpclient/endpoints/exploreChannels.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import {doFetchFromBackend} from '../api';
-import {ExploreChannelRequestPayload} from '../payload';
-import {Channel} from '../model';
-import {ChannelsPayload} from '../payload/ChannelsPayload';
-
-const channelsMapper = (payload: ChannelsPayload, source?: string): Channel[] => {
- return payload.data.map(
- (entry: Channel): Channel => {
- return {
- source,
- ...entry,
- };
- }
- );
-};
-
-export function exploreChannels(requestPayload: ExploreChannelRequestPayload) {
- return doFetchFromBackend('channels.explore', requestPayload)
- .then((response: ChannelsPayload) => {
- const channels = channelsMapper(response, requestPayload.source);
- return channels;
- })
- .catch((error: Error) => {
- return error;
- });
-}
diff --git a/lib/typescript/httpclient/endpoints/index.ts b/lib/typescript/httpclient/endpoints/index.ts
deleted file mode 100644
index cb394896b7..0000000000
--- a/lib/typescript/httpclient/endpoints/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export * from './listChannels';
-export * from './exploreChannels';
-export * from './connectChannel';
-export * from './disconnectChannel';
-export * from './listConversations';
-export * from './listTags';
-export * from './createTag';
-export * from './updateTag';
-export * from './deleteTag';
-export * from './loginViaEmail';
diff --git a/lib/typescript/httpclient/endpoints/listChannels.ts b/lib/typescript/httpclient/endpoints/listChannels.ts
deleted file mode 100644
index bc1e47642e..0000000000
--- a/lib/typescript/httpclient/endpoints/listChannels.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import {doFetchFromBackend} from '../api';
-import {Channel} from '../model';
-import {ChannelsPayload} from '../payload/ChannelsPayload';
-
-const channelsMapper = (payload: ChannelsPayload, source?: string): Channel[] => {
- return payload.data.map(
- (entry: Channel): Channel => {
- return {
- source,
- ...entry,
- };
- }
- );
-};
-
-export function listChannels() {
- return doFetchFromBackend('channels.list')
- .then((response: ChannelsPayload) => {
- const channels = channelsMapper(response);
- return channels;
- })
- .catch((error: Error) => {
- return error;
- });
-}
diff --git a/lib/typescript/httpclient/endpoints/listConversations.ts b/lib/typescript/httpclient/endpoints/listConversations.ts
deleted file mode 100644
index 1234aa1023..0000000000
--- a/lib/typescript/httpclient/endpoints/listConversations.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import {doFetchFromBackend} from '../api';
-import {ListConversationsRequestPayload} from '../payload';
-import {Conversation, Message} from '../model';
-import {ConversationPayload} from '../payload/ConversationPayload';
-import {MessagePayload} from '../payload/MessagePayload';
-import {PaginatedPayload} from '../payload/PaginatedPayload';
-
-const messageMapper = (payload: MessagePayload): Message => {
- const message: Message = {
- id: payload.id,
- content: payload.content,
- state: payload.state,
- alignment: payload.alignment,
- sentAt: payload.sent_at,
- };
- return message;
-};
-
-const conversationMapper = (payload: ConversationPayload): Conversation => {
- const conversation: Conversation = {
- id: payload.id,
- channel: payload.channel,
- createdAt: payload.created_at,
- contact: {
- avatarUrl: payload.contact.avatar_url,
- firstName: payload.contact.first_name,
- lastName: payload.contact.last_name,
- displayName: payload.contact.first_name + ' ' + payload.contact.last_name,
- id: payload.contact.id,
- },
- tags: payload.tags,
- lastMessage: messageMapper(payload.last_message),
- unreadMessageCount: payload.unread_message_count,
- };
- return conversation;
-};
-
-const conversationsMapper = (payloadArray: ConversationPayload[]): Conversation[] => {
- return (payloadArray || []).map(conversation => conversationMapper(conversation));
-};
-
-export function listConversations(conversationListRequest: ListConversationsRequestPayload) {
- conversationListRequest.page_size = conversationListRequest.page_size ?? 10;
- conversationListRequest.cursor = conversationListRequest.cursor ?? null;
-
- return doFetchFromBackend('conversations.list', conversationListRequest)
- .then((response: PaginatedPayload) => {
- const {responseMetadata} = response;
- return {data: conversationsMapper(response.data), metadata: responseMetadata};
- })
- .catch((error: Error) => {
- return error;
- });
-}
diff --git a/lib/typescript/httpclient/endpoints/listTags.ts b/lib/typescript/httpclient/endpoints/listTags.ts
deleted file mode 100644
index 27fe74381b..0000000000
--- a/lib/typescript/httpclient/endpoints/listTags.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import {doFetchFromBackend} from '../api';
-import {Tag} from '../model';
-import {ListTagsResponsePayload} from '../payload';
-
-const tagMapper = {
- BLUE: 'tag-blue',
- RED: 'tag-red',
- GREEN: 'tag-green',
- PURPLE: 'tag-purple',
-};
-
-const tagsMapper = (serverTags: Tag[]): Tag[] => {
- return serverTags.map(t => ({id: t.id, name: t.name, color: tagMapper[t.color] || 'tag-blue'}));
-};
-
-export function listTags() {
- return doFetchFromBackend('tags.list')
- .then((response: ListTagsResponsePayload) => {
- return tagsMapper(response.data);
- })
- .catch((error: Error) => {
- return error;
- });
-}
diff --git a/lib/typescript/httpclient/endpoints/loginViaEmail.ts b/lib/typescript/httpclient/endpoints/loginViaEmail.ts
deleted file mode 100644
index e64db2f026..0000000000
--- a/lib/typescript/httpclient/endpoints/loginViaEmail.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import {doFetchFromBackend} from '../api';
-import {User} from '../model';
-import {LoginViaEmailRequestPayload} from '../payload';
-import {UserPayload} from '../payload/UserPayload';
-
-const userMapper = (payload: UserPayload): User => {
- return {
- id: payload.id,
- firstName: payload.first_name,
- lastName: payload.last_name,
- displayName: payload.first_name + ' ' + payload.last_name,
- token: payload.token,
- };
-};
-
-export function loginViaEmail(requestPayload: LoginViaEmailRequestPayload) {
- return doFetchFromBackend('users.login', requestPayload)
- .then((response: UserPayload) => {
- return userMapper(response);
- })
- .catch((error: Error) => {
- return error;
- });
-}
diff --git a/lib/typescript/httpclient/endpoints/updateTag.ts b/lib/typescript/httpclient/endpoints/updateTag.ts
deleted file mode 100644
index 033a2e6e23..0000000000
--- a/lib/typescript/httpclient/endpoints/updateTag.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import {doFetchFromBackend} from '../api';
-import {Tag} from '../model';
-
-export function updateTag(tag: Tag) {
- return doFetchFromBackend('tags.update', {...tag})
- .then(() => Promise.resolve(true))
- .catch((error: Error) => Promise.reject(error));
-}
diff --git a/lib/typescript/httpclient/index.ts b/lib/typescript/httpclient/index.ts
index ebe9609742..6f1994d915 100644
--- a/lib/typescript/httpclient/index.ts
+++ b/lib/typescript/httpclient/index.ts
@@ -1,32 +1,243 @@
+import {ChannelsPayload} from './payload/ChannelsPayload';
+import {channelsMapper} from './mappers/channelsMapper';
import {
- listChannels,
- exploreChannels,
- connectChannel,
- disconnectChannel,
- listConversations,
- listTags,
- createTag,
- updateTag,
- deleteTag,
- loginViaEmail,
-} from './endpoints';
-
-export const HttpClient = (function() {
- return {
- listChannels: listChannels,
- exploreChannels: exploreChannels,
- connectChannel: connectChannel,
- disconnectChannel: disconnectChannel,
- listConversations: listConversations,
- listTags: listTags,
- createTag: createTag,
- updateTag: updateTag,
- deleteTag: deleteTag,
- loginViaEmail: loginViaEmail,
+ ExploreChannelRequestPayload,
+ ConnectChannelRequestPayload,
+ DisconnectChannelRequestPayload,
+ ListConversationsRequestPayload,
+ ListTagsResponsePayload,
+ CreateTagRequestPayload,
+ LoginViaEmailRequestPayload,
+} from './payload';
+import {ChannelApiPayload} from './payload/ChannelApiPayload';
+import {connectChannelApiMapper} from './mappers/connectChannelApiMapper';
+import {channelMapper} from './mappers/channelMapper';
+import {disconnectChannelApiMapper} from './mappers/disconnectChannelApiMapper';
+import {ConversationPayload} from './payload/ConversationPayload';
+import {PaginatedPayload} from './payload/PaginatedPayload';
+import {conversationsMapper} from './mappers/conversationsMapper';
+import {ListMessagesRequestPayload} from './payload/ListMessagesRequestPayload';
+import {TagConversationRequestPayload} from './payload/TagConversationRequestPayload';
+import {UntagConversationRequestPayload} from './payload/UntagConversationRequestPayload';
+import {MessagePayload} from './payload/MessagePayload';
+import {messageMapperData} from './mappers/messageMapperData';
+import {tagsMapper} from './mappers/tagsMapper';
+import {TagColor, Tag} from './model';
+import {TagPayload} from './payload/TagPayload';
+import {userMapper} from './mappers/userMapper';
+
+const headers = {
+ Accept: 'application/json',
+};
+
+export async function parseBody(response: Response): Promise {
+ if (response.ok) {
+ try {
+ return await response.json();
+ } catch {
+ // NOP
+ }
+ }
+
+ let body = await response.text();
+
+ if (body.length > 0) {
+ body = JSON.parse(body);
+ }
+
+ const errorResponse = {
+ status: response.status,
+ body: body,
};
-})();
-export * from './api';
+ throw errorResponse;
+}
+
+export function isString(object: any) {
+ return typeof object === 'string' || object instanceof String;
+}
+
+export class HttpClient {
+ public readonly token?: string;
+ public readonly apiUrlConfig?: string;
+
+ constructor(token?: string, apiUrlConfig?: string) {
+ this.token = token;
+ this.apiUrlConfig = apiUrlConfig || 'http://api.airy';
+ }
+
+ private async doFetchFromBackend(url: string, body?: Object): Promise {
+ if (this.token) {
+ headers['Authorization'] = this.token;
+ }
+ if (!(body instanceof FormData)) {
+ if (!isString(body)) {
+ body = JSON.stringify(body);
+ }
+ headers['Content-Type'] = 'application/json';
+ }
+
+ const response: Response = await fetch(`${this.apiUrlConfig}/${url}`, {
+ method: 'POST',
+ headers: headers,
+ body: body as BodyInit,
+ });
+
+ return parseBody(response);
+ }
+
+ public async listChannels() {
+ try {
+ const response: ChannelsPayload = await this.doFetchFromBackend('channels.list');
+ return channelsMapper(response);
+ } catch (error) {
+ return error;
+ }
+ }
+
+ public async exploreChannels(requestPayload: ExploreChannelRequestPayload) {
+ try {
+ const response: ChannelsPayload = await this.doFetchFromBackend('channels.explore', requestPayload);
+ return channelsMapper(response, requestPayload.source);
+ } catch (error) {
+ return error;
+ }
+ }
+
+ public async connectChannel(requestPayload: ConnectChannelRequestPayload) {
+ try {
+ const response: ChannelApiPayload = await this.doFetchFromBackend(
+ 'channels.connect',
+ connectChannelApiMapper(requestPayload)
+ );
+ return channelMapper(response);
+ } catch (error) {
+ return error;
+ }
+ }
+
+ public async disconnectChannel(requestPayload: DisconnectChannelRequestPayload) {
+ try {
+ const response: ChannelsPayload = await this.doFetchFromBackend(
+ 'channels.disconnect',
+ disconnectChannelApiMapper(requestPayload)
+ );
+ return channelsMapper(response);
+ } catch (error) {
+ return error;
+ }
+ }
+
+ public async listConversations(conversationListRequest: ListConversationsRequestPayload) {
+ conversationListRequest.page_size = conversationListRequest.page_size ?? 10;
+ conversationListRequest.cursor = conversationListRequest.cursor ?? null;
+ try {
+ const response: PaginatedPayload = await this.doFetchFromBackend(
+ 'conversations.list',
+ conversationListRequest
+ );
+ const {response_metadata} = response;
+ return {data: conversationsMapper(response.data), metadata: response_metadata};
+ } catch (error) {
+ return error;
+ }
+ }
+
+ public async readConversations(conversationId: string) {
+ await this.doFetchFromBackend('conversations.read', {conversation_id: conversationId});
+ return Promise.resolve(true);
+ }
+
+ public async listMessages(conversationListRequest: ListMessagesRequestPayload) {
+ conversationListRequest.pageSize = conversationListRequest.pageSize ?? 10;
+ conversationListRequest.cursor = conversationListRequest.cursor ?? null;
+
+ try {
+ const response: PaginatedPayload = await this.doFetchFromBackend('messages.list', {
+ conversation_id: conversationListRequest.conversationId,
+ cursor: conversationListRequest.cursor,
+ page_size: conversationListRequest.pageSize,
+ });
+ const {response_metadata} = response;
+ return {data: messageMapperData(response), metadata: response_metadata};
+ } catch (error) {
+ return error;
+ }
+ }
+
+ public async listTags() {
+ try {
+ const response: ListTagsResponsePayload = await this.doFetchFromBackend('tags.list');
+ return tagsMapper(response.data);
+ } catch (error) {
+ return error;
+ }
+ }
+
+ public async createTag(requestPayload: CreateTagRequestPayload) {
+ try {
+ const response: TagPayload = await this.doFetchFromBackend('tags.create', requestPayload);
+ return {
+ id: response.id,
+ name: requestPayload.name,
+ color: requestPayload.color as TagColor,
+ };
+ } catch (error) {
+ return error;
+ }
+ }
+
+ public async updateTag(tag: Tag) {
+ try {
+ await this.doFetchFromBackend('tags.update', {...tag});
+ return Promise.resolve(true);
+ } catch (error) {
+ return error;
+ }
+ }
+
+ public async deleteTag(id: string) {
+ try {
+ await this.doFetchFromBackend('tags.delete', {id});
+ return Promise.resolve(true);
+ } catch (error) {
+ return error;
+ }
+ }
+
+ public async loginViaEmail(requestPayload: LoginViaEmailRequestPayload) {
+ try {
+ const response = await this.doFetchFromBackend('users.login', requestPayload);
+ return userMapper(response);
+ } catch (error) {
+ return error;
+ }
+ }
+
+ public async tagConversation(requestPayload: TagConversationRequestPayload) {
+ try {
+ await this.doFetchFromBackend('conversations.tag', {
+ conversation_id: requestPayload.conversationId,
+ tag_id: requestPayload.tagId,
+ });
+ return Promise.resolve(true);
+ } catch (error) {
+ return error;
+ }
+ }
+
+ public async untagConversation(requestPayload: UntagConversationRequestPayload) {
+ try {
+ await this.doFetchFromBackend('conversations.untag', {
+ conversation_id: requestPayload.conversationId,
+ tag_id: requestPayload.tagId,
+ });
+ return Promise.resolve(true);
+ } catch (error) {
+ return error;
+ }
+ }
+}
+
export * from './model';
-export * from './endpoints';
export * from './payload';
diff --git a/lib/typescript/httpclient/mappers/channelMapper.ts b/lib/typescript/httpclient/mappers/channelMapper.ts
new file mode 100644
index 0000000000..b8bf0b19f9
--- /dev/null
+++ b/lib/typescript/httpclient/mappers/channelMapper.ts
@@ -0,0 +1,10 @@
+import {Channel} from '../model';
+import {ChannelApiPayload} from '../payload/ChannelApiPayload';
+
+export const channelMapper = (payload: ChannelApiPayload): Channel => ({
+ name: payload.name,
+ source: payload.source,
+ sourceChannelId: payload.source_channel_id,
+ imageUrl: payload.image_url,
+ connected: true,
+});
diff --git a/lib/typescript/httpclient/mappers/channelsMapper.ts b/lib/typescript/httpclient/mappers/channelsMapper.ts
new file mode 100644
index 0000000000..2980892bb3
--- /dev/null
+++ b/lib/typescript/httpclient/mappers/channelsMapper.ts
@@ -0,0 +1,11 @@
+import {ChannelsPayload} from '../payload/ChannelsPayload';
+import {Channel} from '../model';
+
+export const channelsMapper = (payload: ChannelsPayload, source?: string): Channel[] => {
+ return payload.data.map(
+ (entry: Channel): Channel => ({
+ source,
+ ...entry,
+ })
+ );
+};
diff --git a/lib/typescript/httpclient/mappers/connectChannelApiMapper.ts b/lib/typescript/httpclient/mappers/connectChannelApiMapper.ts
new file mode 100644
index 0000000000..65e3f20b12
--- /dev/null
+++ b/lib/typescript/httpclient/mappers/connectChannelApiMapper.ts
@@ -0,0 +1,9 @@
+import {ConnectChannelRequestPayload, ConnectChannelRequestApiPayload} from '../payload';
+
+export const connectChannelApiMapper = (payload: ConnectChannelRequestPayload): ConnectChannelRequestApiPayload => ({
+ source: payload.source,
+ source_channel_id: payload.sourceChannelId,
+ token: payload.token,
+ name: payload.name,
+ image_url: payload.imageUrl,
+});
diff --git a/lib/typescript/httpclient/mappers/conversationMapper.ts b/lib/typescript/httpclient/mappers/conversationMapper.ts
new file mode 100644
index 0000000000..066e4417dc
--- /dev/null
+++ b/lib/typescript/httpclient/mappers/conversationMapper.ts
@@ -0,0 +1,17 @@
+import {Conversation} from '../model';
+import {ConversationPayload} from '../payload/ConversationPayload';
+import {messageMapper} from './messageMapper';
+
+export const conversationMapper = (payload: ConversationPayload): Conversation => ({
+ id: payload.id,
+ channel: payload.channel,
+ createdAt: payload.created_at,
+ contact: {
+ avatarUrl: payload.contact.avatar_url,
+ displayName: payload.contact.display_name,
+ id: payload.contact.id,
+ },
+ tags: payload.tags,
+ lastMessage: messageMapper(payload.last_message),
+ unreadMessageCount: payload.unread_message_count,
+});
diff --git a/lib/typescript/httpclient/mappers/conversationsMapper.ts b/lib/typescript/httpclient/mappers/conversationsMapper.ts
new file mode 100644
index 0000000000..b17a56d3ec
--- /dev/null
+++ b/lib/typescript/httpclient/mappers/conversationsMapper.ts
@@ -0,0 +1,7 @@
+import {Conversation} from '../model';
+import {ConversationPayload} from '../payload/ConversationPayload';
+import {conversationMapper} from './conversationMapper';
+
+export const conversationsMapper = (payloadArray: ConversationPayload[]): Conversation[] => {
+ return (payloadArray || []).map(conversation => conversationMapper(conversation));
+};
diff --git a/lib/typescript/httpclient/mappers/disconnectChannelApiMapper.ts b/lib/typescript/httpclient/mappers/disconnectChannelApiMapper.ts
new file mode 100644
index 0000000000..0916c16c6b
--- /dev/null
+++ b/lib/typescript/httpclient/mappers/disconnectChannelApiMapper.ts
@@ -0,0 +1,7 @@
+import {DisconnectChannelRequestPayload, DisconnectChannelRequestApiPayload} from '../payload';
+
+export const disconnectChannelApiMapper = (
+ payload: DisconnectChannelRequestPayload
+): DisconnectChannelRequestApiPayload => ({
+ channel_id: payload.channelId,
+});
diff --git a/lib/typescript/httpclient/mappers/messageMapper.ts b/lib/typescript/httpclient/mappers/messageMapper.ts
new file mode 100644
index 0000000000..59fba866f5
--- /dev/null
+++ b/lib/typescript/httpclient/mappers/messageMapper.ts
@@ -0,0 +1,10 @@
+import {MessagePayload} from '../payload/MessagePayload';
+import {Message} from '../model';
+
+export const messageMapper = (payload: MessagePayload): Message => ({
+ id: payload.id,
+ content: payload.content,
+ deliveryState: payload.delivery_state,
+ senderType: payload.sender_type,
+ sentAt: new Date(payload.sent_at),
+});
diff --git a/lib/typescript/httpclient/mappers/messageMapperData.ts b/lib/typescript/httpclient/mappers/messageMapperData.ts
new file mode 100644
index 0000000000..fc480dda91
--- /dev/null
+++ b/lib/typescript/httpclient/mappers/messageMapperData.ts
@@ -0,0 +1,7 @@
+import {MessagePayload} from '../payload/MessagePayload';
+import {Message, MessagePayloadData} from '../model';
+import {messageMapper} from './messageMapper';
+
+export const messageMapperData = (payload: MessagePayloadData): Message[] => {
+ return payload.data.map((messagePayload: MessagePayload) => messageMapper(messagePayload));
+};
diff --git a/lib/typescript/httpclient/mappers/tagsMapper.ts b/lib/typescript/httpclient/mappers/tagsMapper.ts
new file mode 100644
index 0000000000..02d6dec3f3
--- /dev/null
+++ b/lib/typescript/httpclient/mappers/tagsMapper.ts
@@ -0,0 +1,12 @@
+import {Tag} from '../model';
+
+const tagMapper = {
+ BLUE: 'tag-blue',
+ RED: 'tag-red',
+ GREEN: 'tag-green',
+ PURPLE: 'tag-purple',
+};
+
+export const tagsMapper = (serverTags: Tag[]): Tag[] => {
+ return serverTags.map(t => ({id: t.id, name: t.name, color: tagMapper[t.color] || 'tag-blue'}));
+};
diff --git a/lib/typescript/httpclient/mappers/userMapper.ts b/lib/typescript/httpclient/mappers/userMapper.ts
new file mode 100644
index 0000000000..a79115b0bb
--- /dev/null
+++ b/lib/typescript/httpclient/mappers/userMapper.ts
@@ -0,0 +1,10 @@
+import {UserPayload} from '../payload/UserPayload';
+import {User} from '../model';
+
+export const userMapper = (payload: UserPayload): User => ({
+ id: payload.id,
+ firstName: payload.first_name,
+ lastName: payload.last_name,
+ displayName: payload.first_name + ' ' + payload.last_name,
+ token: payload.token,
+});
diff --git a/lib/typescript/httpclient/model/Contact.ts b/lib/typescript/httpclient/model/Contact.ts
index 8e0e4b327b..827cac6189 100644
--- a/lib/typescript/httpclient/model/Contact.ts
+++ b/lib/typescript/httpclient/model/Contact.ts
@@ -2,10 +2,7 @@ import {Tag} from './Tag';
export interface Contact {
id: string;
- info: Dictionary;
- first_name: string;
- last_name: string;
- display_name: string;
- avatar_url: string;
- tags: Tag[];
+ displayName: string;
+ avatarUrl: string;
+ tags?: Tag[];
}
diff --git a/lib/typescript/httpclient/model/Conversation.ts b/lib/typescript/httpclient/model/Conversation.ts
index df10890cfb..927a15e4c1 100644
--- a/lib/typescript/httpclient/model/Conversation.ts
+++ b/lib/typescript/httpclient/model/Conversation.ts
@@ -1,17 +1,12 @@
import {Channel} from './Channel';
+import {Contact} from './Contact';
import {Message} from './Message';
export interface Conversation {
id: string;
channel: Channel;
createdAt: string;
- contact: {
- avatarUrl: string;
- firstName: string;
- lastName: string;
- displayName: string;
- id: string;
- };
+ contact: Contact;
tags: string[];
lastMessage: Message;
unreadMessageCount?: number;
diff --git a/lib/typescript/httpclient/model/Message.ts b/lib/typescript/httpclient/model/Message.ts
index 106d1a6d0c..1b32c14848 100644
--- a/lib/typescript/httpclient/model/Message.ts
+++ b/lib/typescript/httpclient/model/Message.ts
@@ -1,3 +1,5 @@
+import {MessagePayload} from '../payload/MessagePayload';
+
export interface Attachement {
type: string;
payload: {
@@ -28,13 +30,21 @@ export enum MessageState {
delivered = 'DELIVERED',
}
+export enum SenderType {
+ sourceContact = 'source_contact',
+ sourceUser = 'source_user',
+ appUser = 'app_user',
+}
export interface Message {
id: string;
content: {
text: string;
type: MessageType;
};
- state: MessageState;
- alignment: MessageAlignment;
- sentAt: string | Date;
+ deliveryState: MessageState;
+ senderType: SenderType;
+ sentAt: Date;
+}
+export interface MessagePayloadData {
+ data: MessagePayload[];
}
diff --git a/lib/typescript/httpclient/payload/ConversationPayload.ts b/lib/typescript/httpclient/payload/ConversationPayload.ts
index d7bd809c40..7f28245b6c 100644
--- a/lib/typescript/httpclient/payload/ConversationPayload.ts
+++ b/lib/typescript/httpclient/payload/ConversationPayload.ts
@@ -7,8 +7,7 @@ export interface ConversationPayload {
created_at: string;
contact: {
avatar_url: string;
- first_name: string;
- last_name: string;
+ display_name: string;
id: string;
};
tags: string[];
diff --git a/lib/typescript/httpclient/payload/ListMessagesRequestPayload.ts b/lib/typescript/httpclient/payload/ListMessagesRequestPayload.ts
new file mode 100644
index 0000000000..7c4839721d
--- /dev/null
+++ b/lib/typescript/httpclient/payload/ListMessagesRequestPayload.ts
@@ -0,0 +1,5 @@
+export interface ListMessagesRequestPayload {
+ conversationId: string;
+ cursor?: string | null;
+ pageSize?: number;
+}
diff --git a/lib/typescript/httpclient/payload/MessagePayload.ts b/lib/typescript/httpclient/payload/MessagePayload.ts
index 587e3b7ead..5b75c905d0 100644
--- a/lib/typescript/httpclient/payload/MessagePayload.ts
+++ b/lib/typescript/httpclient/payload/MessagePayload.ts
@@ -1,4 +1,4 @@
-import {MessageType, MessageState, MessageAlignment} from '../model';
+import {MessageType, MessageState, SenderType} from '../model';
export interface MessagePayload {
id: string;
@@ -6,7 +6,7 @@ export interface MessagePayload {
text: string;
type: MessageType;
};
- state: MessageState;
- alignment: MessageAlignment;
- sent_at: string | Date;
+ delivery_state: MessageState;
+ sender_type: SenderType;
+ sent_at: Date;
}
diff --git a/lib/typescript/httpclient/payload/PaginatedPayload.ts b/lib/typescript/httpclient/payload/PaginatedPayload.ts
index 327378568f..4afb6d42a5 100644
--- a/lib/typescript/httpclient/payload/PaginatedPayload.ts
+++ b/lib/typescript/httpclient/payload/PaginatedPayload.ts
@@ -1,4 +1,4 @@
export interface PaginatedPayload {
data: T[];
- responseMetadata: {previousCursor: string; nextCursor: string; total: number};
+ response_metadata: {previous_cursor: string; next_cursor: string; total: number};
}
diff --git a/lib/typescript/httpclient/payload/TagConversationRequestPayload.ts b/lib/typescript/httpclient/payload/TagConversationRequestPayload.ts
new file mode 100644
index 0000000000..264411731d
--- /dev/null
+++ b/lib/typescript/httpclient/payload/TagConversationRequestPayload.ts
@@ -0,0 +1,4 @@
+export interface TagConversationRequestPayload {
+ conversationId: string;
+ tagId: string;
+}
diff --git a/lib/typescript/httpclient/payload/UntagConversationRequestPayload.ts b/lib/typescript/httpclient/payload/UntagConversationRequestPayload.ts
new file mode 100644
index 0000000000..60b6ee60c6
--- /dev/null
+++ b/lib/typescript/httpclient/payload/UntagConversationRequestPayload.ts
@@ -0,0 +1,4 @@
+export interface UntagConversationRequestPayload {
+ conversationId: string;
+ tagId: string;
+}
diff --git a/lib/typescript/httpclient/payload/index.ts b/lib/typescript/httpclient/payload/index.ts
index ee0ddcfcd6..dbe7695df8 100644
--- a/lib/typescript/httpclient/payload/index.ts
+++ b/lib/typescript/httpclient/payload/index.ts
@@ -1,9 +1,12 @@
export * from './ConnectChannelRequestApiPayload';
export * from './ConnectChannelRequestPayload';
+export * from './CreateTagRequestPayload';
export * from './DisconnectChannelRequestApiPayload';
export * from './DisconnectChannelRequestPayload';
export * from './ExploreChannelRequestPayload';
-export * from './LoginViaEmailRequestPayload';
-export * from './ListTagsResponsePayload';
-export * from './CreateTagRequestPayload';
export * from './ListConversationsRequestPayload';
+export * from './ListTagsResponsePayload';
+export * from './LoginViaEmailRequestPayload';
+export * from './ResponseMetadataPayload';
+export * from './TagConversationRequestPayload';
+export * from './UntagConversationRequestPayload';
diff --git a/lib/typescript/types/BUILD b/lib/typescript/types/BUILD
index 7fed076ef6..67865228e8 100644
--- a/lib/typescript/types/BUILD
+++ b/lib/typescript/types/BUILD
@@ -5,6 +5,10 @@ package(default_visibility = ["//visibility:public"])
ts_library(
name = "types",
srcs = glob([
- "**/*.d.ts",
+ "**/*.ts",
]),
+ deps = [
+ "@npm//@types/react",
+ "@npm//@types/react-dom",
+ ],
)
diff --git a/lib/typescript/types/content.ts b/lib/typescript/types/content.ts
index d3d788072c..1ad4c12925 100644
--- a/lib/typescript/types/content.ts
+++ b/lib/typescript/types/content.ts
@@ -1,9 +1,31 @@
/* tslint:disable */
/* eslint-disable */
-// Generated using typescript-generator version 2.26.723 on 2020-12-02 10:41:15.
+
+// Generated using typescript-generator version 2.26.723 on 2021-01-19 11:27:56.
+
+export interface Audio extends Content, DataUrl {
+ type: 'audio';
+}
export interface Content {
- type: 'text';
+ type: 'audio' | 'file' | 'image' | 'source.template' | 'text' | 'video';
+}
+
+export interface DataUrl {
+ url: string;
+}
+
+export interface File extends Content, DataUrl {
+ type: 'file';
+}
+
+export interface Image extends Content, DataUrl {
+ type: 'image';
+}
+
+export interface SourceTemplate extends Content {
+ type: 'source.template';
+ payload: any;
}
export interface Text extends Content {
@@ -11,4 +33,8 @@ export interface Text extends Content {
text: string;
}
-export type ContentUnion = Text;
+export interface Video extends Content, DataUrl {
+ type: 'video';
+}
+
+export type ContentUnion = Text | Audio | File | Image | Video | SourceTemplate;
diff --git a/lib/typescript/types/global.d.ts b/lib/typescript/types/global.d.ts
index dfacfac5de..b99178d4e2 100644
--- a/lib/typescript/types/global.d.ts
+++ b/lib/typescript/types/global.d.ts
@@ -1,13 +1,6 @@
///
///
-interface CustomNodeModule extends NodeModule {
- hot: any;
-}
-
-// Hot Module Replacement
-declare let module: CustomNodeModule;
-
declare module '*.gif' {
const src: string;
export default src;
diff --git a/lib/typescript/types/index.ts b/lib/typescript/types/index.ts
new file mode 100644
index 0000000000..7b367d1463
--- /dev/null
+++ b/lib/typescript/types/index.ts
@@ -0,0 +1 @@
+export * from './content';
diff --git a/maven_install.json b/maven_install.json
index edd7cea957..6d9d1796fa 100644
--- a/maven_install.json
+++ b/maven_install.json
@@ -1,6 +1,6 @@
{
"dependency_tree": {
- "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": -1613073853,
+ "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": 1000798703,
"conflict_resolution": {
"com.fasterxml.jackson.core:jackson-annotations:2.10.0": "com.fasterxml.jackson.core:jackson-annotations:2.11.2",
"com.fasterxml.jackson.core:jackson-core:2.10.0": "com.fasterxml.jackson.core:jackson-core:2.11.2",
@@ -68,6 +68,146 @@
"sha256": "72e05e5031508115cafa6092cd53af306c5584957a34012511a20aac5e6c45e5",
"url": "https://repo1.maven.org/maven2/com/101tec/zkclient/0.11/zkclient-0.11.jar"
},
+ {
+ "coord": "com.amazonaws:aws-java-sdk-core:1.11.933",
+ "dependencies": [
+ "com.fasterxml.jackson.core:jackson-core:2.11.2",
+ "commons-logging:commons-logging:1.2",
+ "software.amazon.ion:ion-java:1.0.2",
+ "commons-codec:commons-codec:1.11",
+ "org.apache.httpcomponents:httpcore:4.4.13",
+ "joda-time:joda-time:2.10.2",
+ "com.fasterxml.jackson.core:jackson-annotations:2.11.2",
+ "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:jar:2.6.7",
+ "org.apache.httpcomponents:httpclient:4.5.13",
+ "com.fasterxml.jackson.core:jackson-databind:jar:2.11.2"
+ ],
+ "directDependencies": [
+ "commons-logging:commons-logging:1.2",
+ "software.amazon.ion:ion-java:1.0.2",
+ "joda-time:joda-time:2.10.2",
+ "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:jar:2.6.7",
+ "org.apache.httpcomponents:httpclient:4.5.13",
+ "com.fasterxml.jackson.core:jackson-databind:jar:2.11.2"
+ ],
+ "exclusions": [
+ "ch.qos.logback:logback-classic",
+ "org.springframework.boot:spring-boot-starter-tomcat",
+ "org.springframework.boot:spring-boot-starter-logging",
+ "org.slf4j:slf4j-log4j12"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-core/1.11.933/aws-java-sdk-core-1.11.933.jar",
+ "mirror_urls": [
+ "https://packages.confluent.io/maven/com/amazonaws/aws-java-sdk-core/1.11.933/aws-java-sdk-core-1.11.933.jar",
+ "https://oss.sonatype.org/content/repositories/snapshots/com/amazonaws/aws-java-sdk-core/1.11.933/aws-java-sdk-core-1.11.933.jar",
+ "https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-core/1.11.933/aws-java-sdk-core-1.11.933.jar",
+ "https://jitpack.io/com/amazonaws/aws-java-sdk-core/1.11.933/aws-java-sdk-core-1.11.933.jar"
+ ],
+ "sha256": "3ec5d0fc6a6a605f74f5ac736bd3a96d189c113ee7fd84117b8b1281223c224c",
+ "url": "https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-core/1.11.933/aws-java-sdk-core-1.11.933.jar"
+ },
+ {
+ "coord": "com.amazonaws:aws-java-sdk-kms:1.11.933",
+ "dependencies": [
+ "com.fasterxml.jackson.core:jackson-core:2.11.2",
+ "com.amazonaws:jmespath-java:1.11.933",
+ "com.amazonaws:aws-java-sdk-core:1.11.933",
+ "commons-logging:commons-logging:1.2",
+ "software.amazon.ion:ion-java:1.0.2",
+ "commons-codec:commons-codec:1.11",
+ "org.apache.httpcomponents:httpcore:4.4.13",
+ "joda-time:joda-time:2.10.2",
+ "com.fasterxml.jackson.core:jackson-annotations:2.11.2",
+ "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:jar:2.6.7",
+ "org.apache.httpcomponents:httpclient:4.5.13",
+ "com.fasterxml.jackson.core:jackson-databind:2.11.2",
+ "com.fasterxml.jackson.core:jackson-databind:jar:2.11.2"
+ ],
+ "directDependencies": [
+ "com.amazonaws:aws-java-sdk-core:1.11.933",
+ "com.amazonaws:jmespath-java:1.11.933"
+ ],
+ "exclusions": [
+ "ch.qos.logback:logback-classic",
+ "org.springframework.boot:spring-boot-starter-tomcat",
+ "org.springframework.boot:spring-boot-starter-logging",
+ "org.slf4j:slf4j-log4j12"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-kms/1.11.933/aws-java-sdk-kms-1.11.933.jar",
+ "mirror_urls": [
+ "https://packages.confluent.io/maven/com/amazonaws/aws-java-sdk-kms/1.11.933/aws-java-sdk-kms-1.11.933.jar",
+ "https://oss.sonatype.org/content/repositories/snapshots/com/amazonaws/aws-java-sdk-kms/1.11.933/aws-java-sdk-kms-1.11.933.jar",
+ "https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-kms/1.11.933/aws-java-sdk-kms-1.11.933.jar",
+ "https://jitpack.io/com/amazonaws/aws-java-sdk-kms/1.11.933/aws-java-sdk-kms-1.11.933.jar"
+ ],
+ "sha256": "12311f2824c5fd1a8d8f6aef7fbbf450192c406507003c3aa22aa1e660612035",
+ "url": "https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-kms/1.11.933/aws-java-sdk-kms-1.11.933.jar"
+ },
+ {
+ "coord": "com.amazonaws:aws-java-sdk-s3:1.11.933",
+ "dependencies": [
+ "com.fasterxml.jackson.core:jackson-core:2.11.2",
+ "com.amazonaws:jmespath-java:1.11.933",
+ "com.amazonaws:aws-java-sdk-core:1.11.933",
+ "commons-logging:commons-logging:1.2",
+ "com.amazonaws:aws-java-sdk-kms:1.11.933",
+ "software.amazon.ion:ion-java:1.0.2",
+ "commons-codec:commons-codec:1.11",
+ "org.apache.httpcomponents:httpcore:4.4.13",
+ "joda-time:joda-time:2.10.2",
+ "com.fasterxml.jackson.core:jackson-annotations:2.11.2",
+ "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:jar:2.6.7",
+ "org.apache.httpcomponents:httpclient:4.5.13",
+ "com.fasterxml.jackson.core:jackson-databind:2.11.2",
+ "com.fasterxml.jackson.core:jackson-databind:jar:2.11.2"
+ ],
+ "directDependencies": [
+ "com.amazonaws:aws-java-sdk-core:1.11.933",
+ "com.amazonaws:aws-java-sdk-kms:1.11.933",
+ "com.amazonaws:jmespath-java:1.11.933"
+ ],
+ "exclusions": [
+ "ch.qos.logback:logback-classic",
+ "org.springframework.boot:spring-boot-starter-tomcat",
+ "org.springframework.boot:spring-boot-starter-logging",
+ "org.slf4j:slf4j-log4j12"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-s3/1.11.933/aws-java-sdk-s3-1.11.933.jar",
+ "mirror_urls": [
+ "https://packages.confluent.io/maven/com/amazonaws/aws-java-sdk-s3/1.11.933/aws-java-sdk-s3-1.11.933.jar",
+ "https://oss.sonatype.org/content/repositories/snapshots/com/amazonaws/aws-java-sdk-s3/1.11.933/aws-java-sdk-s3-1.11.933.jar",
+ "https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-s3/1.11.933/aws-java-sdk-s3-1.11.933.jar",
+ "https://jitpack.io/com/amazonaws/aws-java-sdk-s3/1.11.933/aws-java-sdk-s3-1.11.933.jar"
+ ],
+ "sha256": "8201d0e4db03e80050bf6e57009e3a71d47c31f4fe9e5bba3faa19ae5099a035",
+ "url": "https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-s3/1.11.933/aws-java-sdk-s3-1.11.933.jar"
+ },
+ {
+ "coord": "com.amazonaws:jmespath-java:1.11.933",
+ "dependencies": [
+ "com.fasterxml.jackson.core:jackson-databind:2.11.2",
+ "com.fasterxml.jackson.core:jackson-core:2.11.2",
+ "com.fasterxml.jackson.core:jackson-annotations:2.11.2"
+ ],
+ "directDependencies": [
+ "com.fasterxml.jackson.core:jackson-databind:2.11.2"
+ ],
+ "exclusions": [
+ "ch.qos.logback:logback-classic",
+ "org.springframework.boot:spring-boot-starter-tomcat",
+ "org.springframework.boot:spring-boot-starter-logging",
+ "org.slf4j:slf4j-log4j12"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/com/amazonaws/jmespath-java/1.11.933/jmespath-java-1.11.933.jar",
+ "mirror_urls": [
+ "https://packages.confluent.io/maven/com/amazonaws/jmespath-java/1.11.933/jmespath-java-1.11.933.jar",
+ "https://oss.sonatype.org/content/repositories/snapshots/com/amazonaws/jmespath-java/1.11.933/jmespath-java-1.11.933.jar",
+ "https://repo1.maven.org/maven2/com/amazonaws/jmespath-java/1.11.933/jmespath-java-1.11.933.jar",
+ "https://jitpack.io/com/amazonaws/jmespath-java/1.11.933/jmespath-java-1.11.933.jar"
+ ],
+ "sha256": "e8752d6d6f857f86c886957bbc20160b2fd22750d10f28f2bbac783cc8351ff7",
+ "url": "https://repo1.maven.org/maven2/com/amazonaws/jmespath-java/1.11.933/jmespath-java-1.11.933.jar"
+ },
{
"coord": "com.cedarsoftware:java-util:1.34.0",
"dependencies": [],
@@ -179,6 +319,30 @@
"sha256": "cb890b4aad8ed21a7b57e3c8f7924dbdca1aeff9ddd27cb0ff37243037ae1342",
"url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.11.2/jackson-databind-2.11.2.jar"
},
+ {
+ "coord": "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:jar:2.6.7",
+ "dependencies": [
+ "com.fasterxml.jackson.core:jackson-core:2.11.2"
+ ],
+ "directDependencies": [
+ "com.fasterxml.jackson.core:jackson-core:2.11.2"
+ ],
+ "exclusions": [
+ "ch.qos.logback:logback-classic",
+ "org.springframework.boot:spring-boot-starter-tomcat",
+ "org.springframework.boot:spring-boot-starter-logging",
+ "org.slf4j:slf4j-log4j12"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.6.7/jackson-dataformat-cbor-2.6.7.jar",
+ "mirror_urls": [
+ "https://packages.confluent.io/maven/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.6.7/jackson-dataformat-cbor-2.6.7.jar",
+ "https://oss.sonatype.org/content/repositories/snapshots/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.6.7/jackson-dataformat-cbor-2.6.7.jar",
+ "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.6.7/jackson-dataformat-cbor-2.6.7.jar",
+ "https://jitpack.io/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.6.7/jackson-dataformat-cbor-2.6.7.jar"
+ ],
+ "sha256": "956a0fb9186a796b8a6548909da1ee55004279647e261c7f540e5d49d4f199bf",
+ "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.6.7/jackson-dataformat-cbor-2.6.7.jar"
+ },
{
"coord": "com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.10.2",
"dependencies": [
@@ -702,7 +866,6 @@
"dependencies": [
"com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava",
"com.fasterxml.jackson.core:jackson-core:2.11.2",
- "org.apache.httpcomponents:httpclient:4.5.10",
"com.google.j2objc:j2objc-annotations:1.3",
"commons-logging:commons-logging:1.2",
"io.opencensus:opencensus-contrib-http-util:0.24.0",
@@ -712,12 +875,13 @@
"commons-codec:commons-codec:1.11",
"io.opencensus:opencensus-api:0.24.0",
"io.grpc:grpc-context:1.22.1",
+ "org.apache.httpcomponents:httpcore:4.4.13",
"com.google.http-client:google-http-client-jackson2:1.34.0",
"com.google.errorprone:error_prone_annotations:2.3.4",
"com.google.http-client:google-http-client:1.34.0",
"com.google.guava:failureaccess:1.0.1",
"com.google.guava:guava:29.0-jre",
- "org.apache.httpcomponents:httpcore:4.4.12",
+ "org.apache.httpcomponents:httpclient:4.5.13",
"org.checkerframework:checker-qual:2.11.1"
],
"directDependencies": [
@@ -903,7 +1067,6 @@
"dependencies": [
"com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava",
"com.fasterxml.jackson.core:jackson-core:2.11.2",
- "org.apache.httpcomponents:httpclient:4.5.10",
"com.google.j2objc:j2objc-annotations:1.3",
"commons-logging:commons-logging:1.2",
"io.opencensus:opencensus-contrib-http-util:0.24.0",
@@ -911,11 +1074,12 @@
"commons-codec:commons-codec:1.11",
"io.opencensus:opencensus-api:0.24.0",
"io.grpc:grpc-context:1.22.1",
+ "org.apache.httpcomponents:httpcore:4.4.13",
"com.google.errorprone:error_prone_annotations:2.3.4",
"com.google.http-client:google-http-client:1.34.0",
"com.google.guava:failureaccess:1.0.1",
"com.google.guava:guava:29.0-jre",
- "org.apache.httpcomponents:httpcore:4.4.12",
+ "org.apache.httpcomponents:httpclient:4.5.13",
"org.checkerframework:checker-qual:2.11.1"
],
"directDependencies": [
@@ -942,7 +1106,6 @@
"coord": "com.google.http-client:google-http-client:1.34.0",
"dependencies": [
"com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava",
- "org.apache.httpcomponents:httpclient:4.5.10",
"com.google.j2objc:j2objc-annotations:1.3",
"commons-logging:commons-logging:1.2",
"io.opencensus:opencensus-contrib-http-util:0.24.0",
@@ -950,20 +1113,21 @@
"commons-codec:commons-codec:1.11",
"io.opencensus:opencensus-api:0.24.0",
"io.grpc:grpc-context:1.22.1",
+ "org.apache.httpcomponents:httpcore:4.4.13",
"com.google.errorprone:error_prone_annotations:2.3.4",
"com.google.guava:failureaccess:1.0.1",
"com.google.guava:guava:29.0-jre",
- "org.apache.httpcomponents:httpcore:4.4.12",
+ "org.apache.httpcomponents:httpclient:4.5.13",
"org.checkerframework:checker-qual:2.11.1"
],
"directDependencies": [
- "org.apache.httpcomponents:httpclient:4.5.10",
"com.google.j2objc:j2objc-annotations:1.3",
"io.opencensus:opencensus-contrib-http-util:0.24.0",
"com.google.code.findbugs:jsr305:3.0.2",
"io.opencensus:opencensus-api:0.24.0",
+ "org.apache.httpcomponents:httpcore:4.4.13",
"com.google.guava:guava:29.0-jre",
- "org.apache.httpcomponents:httpcore:4.4.12"
+ "org.apache.httpcomponents:httpclient:4.5.13"
],
"exclusions": [
"ch.qos.logback:logback-classic",
@@ -1447,7 +1611,6 @@
"dependencies": [
"com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava",
"com.fasterxml.jackson.core:jackson-core:2.11.2",
- "org.apache.httpcomponents:httpclient:4.5.10",
"javax.xml.bind:jaxb-api:2.3.1",
"com.google.j2objc:j2objc-annotations:1.3",
"commons-logging:commons-logging:1.2",
@@ -1455,27 +1618,28 @@
"com.google.code.findbugs:jsr305:3.0.2",
"javax.activation:javax.activation-api:1.2.0",
"io.jsonwebtoken:jjwt-jackson:0.10.7",
+ "org.apache.httpcomponents:httpcore:4.4.13",
"com.google.errorprone:error_prone_annotations:2.3.4",
"io.jsonwebtoken:jjwt-api:0.10.7",
"joda-time:joda-time:2.10.2",
"com.fasterxml.jackson.core:jackson-annotations:2.11.2",
"com.google.guava:failureaccess:1.0.1",
"com.google.guava:guava:29.0-jre",
- "org.apache.httpcomponents:httpcore:4.4.12",
+ "org.apache.httpcomponents:httpclient:4.5.13",
"com.fasterxml.jackson.core:jackson-databind:2.11.2",
"org.checkerframework:checker-qual:2.11.1"
],
"directDependencies": [
"com.fasterxml.jackson.core:jackson-core:2.11.2",
- "org.apache.httpcomponents:httpclient:4.5.10",
"javax.xml.bind:jaxb-api:2.3.1",
"io.jsonwebtoken:jjwt-impl:0.10.7",
"io.jsonwebtoken:jjwt-jackson:0.10.7",
+ "org.apache.httpcomponents:httpcore:4.4.13",
"io.jsonwebtoken:jjwt-api:0.10.7",
"joda-time:joda-time:2.10.2",
"com.fasterxml.jackson.core:jackson-annotations:2.11.2",
"com.google.guava:guava:29.0-jre",
- "org.apache.httpcomponents:httpcore:4.4.12",
+ "org.apache.httpcomponents:httpclient:4.5.13",
"com.fasterxml.jackson.core:jackson-databind:2.11.2"
],
"exclusions": [
@@ -4560,34 +4724,35 @@
"url": "https://repo1.maven.org/maven2/org/apache/curator/curator-test/4.2.0/curator-test-4.2.0.jar"
},
{
- "coord": "org.apache.httpcomponents:httpclient:4.5.10",
+ "coord": "org.apache.httpcomponents:httpclient:4.5.13",
"dependencies": [
+ "org.apache.httpcomponents:httpcore:4.4.13",
"commons-logging:commons-logging:1.2",
- "org.apache.httpcomponents:httpcore:4.4.12"
+ "commons-codec:commons-codec:1.11"
],
"directDependencies": [
+ "commons-codec:commons-codec:1.11",
"commons-logging:commons-logging:1.2",
- "org.apache.httpcomponents:httpcore:4.4.12"
+ "org.apache.httpcomponents:httpcore:4.4.13"
],
"exclusions": [
- "org.slf4j:slf4j-log4j12",
- "commons-codec:commons-codec",
- "org.springframework.boot:spring-boot-starter-tomcat",
"ch.qos.logback:logback-classic",
- "org.springframework.boot:spring-boot-starter-logging"
+ "org.springframework.boot:spring-boot-starter-tomcat",
+ "org.springframework.boot:spring-boot-starter-logging",
+ "org.slf4j:slf4j-log4j12"
],
- "file": "v1/https/repo1.maven.org/maven2/org/apache/httpcomponents/httpclient/4.5.10/httpclient-4.5.10.jar",
+ "file": "v1/https/repo1.maven.org/maven2/org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar",
"mirror_urls": [
- "https://packages.confluent.io/maven/org/apache/httpcomponents/httpclient/4.5.10/httpclient-4.5.10.jar",
- "https://oss.sonatype.org/content/repositories/snapshots/org/apache/httpcomponents/httpclient/4.5.10/httpclient-4.5.10.jar",
- "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpclient/4.5.10/httpclient-4.5.10.jar",
- "https://jitpack.io/org/apache/httpcomponents/httpclient/4.5.10/httpclient-4.5.10.jar"
+ "https://packages.confluent.io/maven/org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar",
+ "https://oss.sonatype.org/content/repositories/snapshots/org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar",
+ "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar",
+ "https://jitpack.io/org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar"
],
- "sha256": "38b9f16f504928e4db736a433b9cd10968d9ec8d6f5d0e61a64889a689172134",
- "url": "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpclient/4.5.10/httpclient-4.5.10.jar"
+ "sha256": "6fe9026a566c6a5001608cf3fc32196641f6c1e5e1986d1037ccdbd5f31ef743",
+ "url": "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar"
},
{
- "coord": "org.apache.httpcomponents:httpcore:4.4.12",
+ "coord": "org.apache.httpcomponents:httpcore:4.4.13",
"dependencies": [],
"directDependencies": [],
"exclusions": [
@@ -4596,15 +4761,15 @@
"org.springframework.boot:spring-boot-starter-logging",
"org.slf4j:slf4j-log4j12"
],
- "file": "v1/https/repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.12/httpcore-4.4.12.jar",
+ "file": "v1/https/repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar",
"mirror_urls": [
- "https://packages.confluent.io/maven/org/apache/httpcomponents/httpcore/4.4.12/httpcore-4.4.12.jar",
- "https://oss.sonatype.org/content/repositories/snapshots/org/apache/httpcomponents/httpcore/4.4.12/httpcore-4.4.12.jar",
- "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.12/httpcore-4.4.12.jar",
- "https://jitpack.io/org/apache/httpcomponents/httpcore/4.4.12/httpcore-4.4.12.jar"
+ "https://packages.confluent.io/maven/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar",
+ "https://oss.sonatype.org/content/repositories/snapshots/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar",
+ "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar",
+ "https://jitpack.io/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar"
],
- "sha256": "ab765334beabf0ea024484a5e90a7c40e8160b145f22d199e11e27f68d57da08",
- "url": "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.12/httpcore-4.4.12.jar"
+ "sha256": "e06e89d40943245fcfa39ec537cdbfce3762aecde8f9c597780d2b00c2b43424",
+ "url": "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar"
},
{
"coord": "org.apache.kafka:connect-api:2.5.1",
@@ -5207,6 +5372,26 @@
"sha256": "a9aae9ff8ae3e17a2a18f79175e82b16267c246fbbd3ca9dfbbb290b08dcfdd4",
"url": "https://repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar"
},
+ {
+ "coord": "org.aspectj:aspectjweaver:1.8.10",
+ "dependencies": [],
+ "directDependencies": [],
+ "exclusions": [
+ "ch.qos.logback:logback-classic",
+ "org.springframework.boot:spring-boot-starter-tomcat",
+ "org.springframework.boot:spring-boot-starter-logging",
+ "org.slf4j:slf4j-log4j12"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/org/aspectj/aspectjweaver/1.8.10/aspectjweaver-1.8.10.jar",
+ "mirror_urls": [
+ "https://packages.confluent.io/maven/org/aspectj/aspectjweaver/1.8.10/aspectjweaver-1.8.10.jar",
+ "https://oss.sonatype.org/content/repositories/snapshots/org/aspectj/aspectjweaver/1.8.10/aspectjweaver-1.8.10.jar",
+ "https://repo1.maven.org/maven2/org/aspectj/aspectjweaver/1.8.10/aspectjweaver-1.8.10.jar",
+ "https://jitpack.io/org/aspectj/aspectjweaver/1.8.10/aspectjweaver-1.8.10.jar"
+ ],
+ "sha256": "9687a76555ae2fc334ed6434343c62b17e04e1be86ca473149b6c9469405ecf7",
+ "url": "https://repo1.maven.org/maven2/org/aspectj/aspectjweaver/1.8.10/aspectjweaver-1.8.10.jar"
+ },
{
"coord": "org.assertj:assertj-core:3.16.1",
"dependencies": [],
@@ -9100,6 +9285,31 @@
"sha256": "6c02c06ee4c9f989d48d30d3b17cf90fe07b19ed39596e2ede69b1c674acaa97",
"url": "https://repo1.maven.org/maven2/org/springframework/data/spring-data-relational/2.0.1.RELEASE/spring-data-relational-2.0.1.RELEASE.jar"
},
+ {
+ "coord": "org.springframework.retry:spring-retry:1.2.5.RELEASE",
+ "dependencies": [
+ "org.springframework:spring-jcl:5.2.8.RELEASE",
+ "org.springframework:spring-core:5.2.8.RELEASE"
+ ],
+ "directDependencies": [
+ "org.springframework:spring-core:5.2.8.RELEASE"
+ ],
+ "exclusions": [
+ "ch.qos.logback:logback-classic",
+ "org.springframework.boot:spring-boot-starter-tomcat",
+ "org.springframework.boot:spring-boot-starter-logging",
+ "org.slf4j:slf4j-log4j12"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/org/springframework/retry/spring-retry/1.2.5.RELEASE/spring-retry-1.2.5.RELEASE.jar",
+ "mirror_urls": [
+ "https://packages.confluent.io/maven/org/springframework/retry/spring-retry/1.2.5.RELEASE/spring-retry-1.2.5.RELEASE.jar",
+ "https://oss.sonatype.org/content/repositories/snapshots/org/springframework/retry/spring-retry/1.2.5.RELEASE/spring-retry-1.2.5.RELEASE.jar",
+ "https://repo1.maven.org/maven2/org/springframework/retry/spring-retry/1.2.5.RELEASE/spring-retry-1.2.5.RELEASE.jar",
+ "https://jitpack.io/org/springframework/retry/spring-retry/1.2.5.RELEASE/spring-retry-1.2.5.RELEASE.jar"
+ ],
+ "sha256": "71e7cb0d33e3f595011d3e98b14f41ca165a435760ecd4d68cb935e8afa8a3d2",
+ "url": "https://repo1.maven.org/maven2/org/springframework/retry/spring-retry/1.2.5.RELEASE/spring-retry-1.2.5.RELEASE.jar"
+ },
{
"coord": "org.springframework.security:spring-security-config:5.3.3.RELEASE",
"dependencies": [
@@ -9716,6 +9926,26 @@
],
"sha256": "d87d607e500885356c03c1cae61e8c2e05d697df8787d5aba13484c2eb76a844",
"url": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/1.26/snakeyaml-1.26.jar"
+ },
+ {
+ "coord": "software.amazon.ion:ion-java:1.0.2",
+ "dependencies": [],
+ "directDependencies": [],
+ "exclusions": [
+ "ch.qos.logback:logback-classic",
+ "org.springframework.boot:spring-boot-starter-tomcat",
+ "org.springframework.boot:spring-boot-starter-logging",
+ "org.slf4j:slf4j-log4j12"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/software/amazon/ion/ion-java/1.0.2/ion-java-1.0.2.jar",
+ "mirror_urls": [
+ "https://packages.confluent.io/maven/software/amazon/ion/ion-java/1.0.2/ion-java-1.0.2.jar",
+ "https://oss.sonatype.org/content/repositories/snapshots/software/amazon/ion/ion-java/1.0.2/ion-java-1.0.2.jar",
+ "https://repo1.maven.org/maven2/software/amazon/ion/ion-java/1.0.2/ion-java-1.0.2.jar",
+ "https://jitpack.io/software/amazon/ion/ion-java/1.0.2/ion-java-1.0.2.jar"
+ ],
+ "sha256": "0d127b205a1fce0abc2a3757a041748651bc66c15cf4c059bac5833b27d471a5",
+ "url": "https://repo1.maven.org/maven2/software/amazon/ion/ion-java/1.0.2/ion-java-1.0.2.jar"
}
],
"version": "0.1.0"
diff --git a/package.json b/package.json
index 9bf313abec..204a88155a 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,9 @@
"lodash-es": "^4.17.15",
"react-window": "1.8.5",
"react-window-infinite-loader": "1.0.5",
+
"reselect": "4.0.0"
+
},
"devDependencies": {
"@babel/core": "7.8.4",
diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh
index 9cbff712af..2f0b1625b8 100755
--- a/scripts/bootstrap.sh
+++ b/scripts/bootstrap.sh
@@ -59,7 +59,7 @@ read -p "Do you want to add the vagrant box to the host file so you can access i
echo
if [[ $REPLY =~ ^[Yy]$ ]];
then
- vagrant plugin install vagrant-hostsupdater --plugin-clean-sources --plugin-source https://gems.ruby-china.com
+ vagrant plugin install vagrant-hostsupdater || vagrant plugin install vagrant-hostsupdater --plugin-clean-sources --plugin-source https://gems.ruby-china.com
fi
if ! command -v VBoxManage &> /dev/null
@@ -130,16 +130,17 @@ if [ -z ${AIRY_VERSION+x} ]; then
branch_name=${branch_name##refs/heads/}
case "$branch_name" in
- develop )
- AIRY_VERSION=beta
- ;;
- release* )
- AIRY_VERSION=release
+ main|release* )
+ AIRY_VERSION=(`cat ../VERSION`)
;;
* )
- AIRY_VERSION=latest
+ AIRY_VERSION=develop
;;
esac
fi
AIRY_VERSION=${AIRY_VERSION} vagrant up
+
+mkdir -p ~/.airy
+cd $infra_path
+vagrant ssh -c "cat /etc/rancher/k3s/k3s.yaml" 2>/dev/null | sed "s/127.0.0.1/192.168.50.5/g" > ~/.airy/kube.conf
diff --git a/scripts/push-images.sh b/scripts/push-images.sh
index 878c725e40..d170851184 100755
--- a/scripts/push-images.sh
+++ b/scripts/push-images.sh
@@ -9,14 +9,10 @@ echo "Branch target: ${BRANCH_TARGET}"
case ${BRANCH_TARGET} in
develop)
- tag="beta"
+ tag="develop"
;;
- main)
- tag="latest"
- ;;
-
- release)
+ main|release)
tag="release"
;;
esac
diff --git a/scripts/release.sh b/scripts/release.sh
index 922423c4ce..2f87070569 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -8,6 +8,8 @@ start() {
echo -e "Starting release ${release_number}\n"
create_issue
create_release_branch
+ update_release_version
+ commit_version
}
create_issue() {
@@ -31,14 +33,13 @@ create_release_branch() {
finish() {
release_number=$1
echo -e "Finishing release ${release_number}\n"
- increase_version
- commit_version
merge_main
merge_develop
echo -e "Release ${release_number} is finished\n"
+ create_alpha_version
}
-increase_version() {
+update_release_version() {
issue_number=$(curl -s\
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/airyhq/airy/issues?labels=release" | jq '.[0].number')
@@ -53,6 +54,21 @@ commit_version() {
echo -e "Updated VERSION file\n"
}
+create_alpha_version() {
+ regex="([0-9]+).([0-9]+).([0-9]+)"
+ if [[ $release_number =~ $regex ]]; then
+ major="${BASH_REMATCH[1]}"
+ minor="${BASH_REMATCH[2]}"
+ patch="${BASH_REMATCH[3]}"
+ fi
+ alpha_version=$(printf "%s.%s.%s.alpha\n" $major $((minor+1)) $patch)
+ command echo ${alpha_version}> VERSION
+ command git add VERSION
+ command git commit -m "Bump version to ${alpha_version}"
+ command git push origin develop
+ echo -e "Updated VERSION file to ${alpha_version}\n"
+}
+
merge_main() {
command git checkout main
command git pull origin main
@@ -71,18 +87,16 @@ merge_develop() {
echo -e "Successfully merged into develop branch\n"
}
-if [[ -b $1 ]] && [[ -b $2 ]];
-then
- case $1 in
- "start")
- start $2
- ;;
- "finish")
- finish $2
- esac
-else
+if [[ -z ${1+x} || -z ${2+x} ]]; then
echo -ne "Error executing script\n"
echo -ne "Expected syntax: release.sh \n"
exit 1
fi
+case $1 in
+ "start")
+ start $2
+ ;;
+ "finish")
+ finish $2
+esac
diff --git a/tools/build/status.sh b/tools/build/bazel_status.sh
similarity index 100%
rename from tools/build/status.sh
rename to tools/build/bazel_status.sh
diff --git a/tools/build/container_push.bzl b/tools/build/container_push.bzl
index 460cc9426f..a8c90a42a3 100644
--- a/tools/build/container_push.bzl
+++ b/tools/build/container_push.bzl
@@ -1,19 +1,14 @@
load("@io_bazel_rules_docker//container:container.bzl", lib_push = "container_push")
-tags_to_push = ["release", "latest", "beta"]
-
def container_push(registry, repository):
- [
- lib_push(
- name = tag,
- format = "Docker",
- image = ":image",
- registry = registry,
- repository = repository,
- tag = tag,
- )
- for tag in tags_to_push
- ]
+ lib_push(
+ name = "develop",
+ format = "Docker",
+ image = ":image",
+ registry = registry,
+ repository = repository,
+ tag = "develop",
+ )
lib_push(
name = "local",
@@ -23,3 +18,12 @@ def container_push(registry, repository):
repository = repository,
tag = "{BUILD_USER}",
)
+
+ lib_push(
+ name = "release",
+ format = "Docker",
+ image = ":image",
+ registry = registry,
+ repository = repository,
+ tag = "{STABLE_VERSION}",
+ )
diff --git a/tsconfig.json b/tsconfig.json
index f9c645c902..77d800bb15 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,6 +22,18 @@
"components/*": [
"./frontend/components/src/*"
],
+ "types": [
+ "./lib/typescript/types"
+ ],
+ "types/*": [
+ "./lib/typescript/types/*"
+ ],
+ "httpclient": [
+ "./lib/typescript/httpclient"
+ ],
+ "httpclient/*": [
+ "./lib/typescript/httpclient/*"
+ ],
"*": [
"./*"
]
diff --git a/yarn.lock b/yarn.lock
index f4d6c9dfa6..5bc1b62f4a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3,9 +3,9 @@
"@airyhq/components@latest":
- version "0.4.8"
- resolved "https://registry.yarnpkg.com/@airyhq/components/-/components-0.4.8.tgz#5fca05ebbcd1195d70075b35c1f48cc32b3b0056"
- integrity sha512-qxdNLgMukxHq5cFacdOTPRC/Km8zHrJf0y2UoE0KcCfPMc7dzh1ziM5EFoHtxsIQJkhPlzcYyNaZxA6IpTdBLQ==
+ version "0.4.11"
+ resolved "https://registry.yarnpkg.com/@airyhq/components/-/components-0.4.11.tgz#68e803bb502ae201f64199c025878fd360f51577"
+ integrity sha512-aZkv/ncpbMeCHaIstuPDPliVZxKjOBDyVeFFUfu+/Y1dA6TOTc3JRGxKQ0wQqMtYrycV+FIuktnszCcIgswzLA==
dependencies:
"@crello/react-lottie" "^0.0.9"
emoji-mart "^3.0.0"
@@ -7193,12 +7193,12 @@ react-hot-loader@^4.12.20:
shallowequal "^1.1.0"
source-map "^0.7.3"
-react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
+react-is@^16.6.0, react-is@^16.7.0:
version "16.12.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==
-react-is@^16.9.0:
+react-is@^16.8.1, react-is@^16.9.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -7209,9 +7209,9 @@ react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-modal@^3.11.2:
- version "3.11.2"
- resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.11.2.tgz#bad911976d4add31aa30dba8a41d11e21c4ac8a4"
- integrity sha512-o8gvvCOFaG1T7W6JUvsYjRjMVToLZgLIsi5kdhFIQCtHxDkA47LznX62j+l6YQkpXDbvQegsDyxe/+JJsFQN7w==
+ version "3.12.1"
+ resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.12.1.tgz#38c33f70d81c33d02ff1ed115530443a3dc2afd3"
+ integrity sha512-WGuXn7Fq31PbFJwtWmOk+jFtGC7E9tJVbFX0lts8ZoS5EPi9+WWylUJWLKKVm3H4GlQ7ZxY7R6tLlbSIBQ5oZA==
dependencies:
exenv "^1.2.0"
prop-types "^15.5.10"
@@ -7423,16 +7423,11 @@ regenerator-runtime@^0.13.2:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==
-regenerator-runtime@^0.13.4:
+regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.5:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
-regenerator-runtime@^0.13.5:
- version "0.13.5"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
- integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
-
regenerator-transform@^0.14.0:
version "0.14.1"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb"