Skip to content

Commit 0f3dcb7

Browse files
feat(flights): new logic (#31)
1 parent 164c02d commit 0f3dcb7

File tree

13 files changed

+387
-207
lines changed

13 files changed

+387
-207
lines changed

.github/workflows/main.yml

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ on:
77
pull_request:
88
types: [ labeled ]
99

10+
permissions:
11+
id-token: write
12+
contents: read
13+
1014
jobs:
1115

1216
push:
@@ -48,38 +52,33 @@ jobs:
4852
working-directory: terraform
4953
run: terraform fmt -check
5054

55+
- name: Configure AWS credentials
56+
uses: aws-actions/configure-aws-credentials@v1
57+
with:
58+
aws-region: "us-east-1"
59+
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
60+
role-session-name: "fly"
61+
role-duration-seconds: 900
62+
5163
# Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc.
5264
- name: Terraform Init
5365
run: terraform init
5466
working-directory: terraform
55-
env:
56-
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
57-
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
5867

5968
- name: Terraform plan
6069
run: terraform plan
6170
working-directory: terraform
62-
env:
63-
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
64-
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
6571

6672
# On push to main, build or change infrastructure according to Terraform configuration files
6773
# Note: It is recommended to set up a required "strict" status check in your repository for "Terraform Cloud". See the documentation on "strict" required status checks for more information: https://help.github.com/en/github/administering-a-repository/types-of-required-status-checks
6874
- name: Terraform Apply
6975
if: startsWith(github.ref, 'refs/tags/')
7076
working-directory: terraform
7177
run: terraform apply -auto-approve
72-
env:
73-
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
74-
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
7578

7679
# Create Cloudfront invalidation
7780
- run: aws cloudfront create-invalidation --distribution-id E1N3PITIUX8V3S --paths "/*"
7881
if: startsWith(github.ref, 'refs/tags/')
79-
env:
80-
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
81-
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
82-
AWS_DEFAULT_REGION: "us-east-1"
8382

8483
- name: Create release version string
8584
if: startsWith(github.ref, 'refs/tags/')

README.MD

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,34 @@ Defaults can be set/unset via:
4646
- `fly unset {key}`
4747

4848
### How it works
49-
Look at the `gh` logical flight as an example. Its logic is as follows: `https://github.com/${gh-org}/${repo}`.
49+
Look at the `gh` logical flight as an example. Its logic is as follows: `https://github.com/${gh-org:plain}/${repo:plain}`.
5050

51-
If no defaults are specified, the extension will assume you will pass in at least 2 parameters to the flight.
52-
`fly gh Apollorion manifests.io` is calculated positionally. `Apollorion` (the first parameter) will replace `${gh-org}` (the first variable)
53-
and `manifests.io` (the second parameter) will replace `${repo}` (the second variable).
51+
If no defaults are specified, the extension will assume you will pass in 2 parameters to the flight.
52+
`fly gh Apollorion manifests.io` is calculated positionally. `Apollorion` (the first parameter) will replace `${gh-org:plain}` (the first variable)
53+
and `manifests.io` (the second parameter) will replace `${repo:plain}` (the second variable).
5454
Which will fly to `https://github.com/Apollorion/manifests.io`.
5555

5656
You can, optionally, set a default for any variable in a logical flight.
5757
`fly set gh-org Apollorion` will set the default for `gh-org` to `Apollorion`.
58-
Now, when you fly the `gh` flight, it will first replace `${gh-org}` with `Apollorion` and then any remaining parameters will be replaced positionally, like above.
58+
Now, when you fly the `gh` flight, it will first replace `${gh-org:plain}` with `Apollorion` and then any remaining parameters will be replaced positionally, like above.
5959
So you could do `fly gh manifests.io` and it will fly to `https://github.com/Apollorion/manifests.io`.
6060

6161
Note: ANY unset parameters will be replaced positionally, in order.
62-
If you have a logical flight that is defined as `https://my.com/{$thing1}/{$thing2}/{$thing3}`, and you only set the default for `thing1`.
62+
If you have a logical flight that is defined as `https://my.com/${thing1:plain}/${thing2:plain}/${thing3:plain}`, and you only set the default for `thing1`.
6363
The extension will expect you to pass in 2 parameters at flight time, otherwise a default flight will be used or an error will be thrown.
6464
`fly my.com test1 test2` will fly to `https://my.com/{thing1 default}/test1/test2/`.
6565
`fly my.com test` will either go to a defined default flight or throw an error.
6666

67+
### Types
68+
Types are useful when you want the application to do something to a string before its passed to the browser.
69+
`plain` will pass the value as plain text (I.E. it will do nothing)
70+
`urlencode` will url encode the value
71+
72+
An example of this would be the built in `ghcs` flight. When searching github code search, the values need to be url encoded.
73+
So when you query ghcs the user can pass in plain text, but it will be url encoded for the browser.
74+
75+
Currently, only `plain` and `urlencode` are supported. All variables must have a type defined.
76+
6777
# Custom flight repos
6878
You can host a custom flight repo and point the extension to it. This will allow you to define standard and logical flights without contributing to this project.
6979

@@ -77,10 +87,10 @@ The custom flight repo has 2 requirements.
7787
2. It has the below structure:
7888
```typescript
7989
interface CustomFlightRepo {
80-
version: string, // Currently supported version is `1`
90+
version: string, // Currently supported version is `2`
8191
logical: {
8292
[key: string]: {
83-
logic: string,
93+
logic: string[],
8494
override?: string
8595
}
8696
},
@@ -89,13 +99,21 @@ interface CustomFlightRepo {
8999
}
90100
}
91101
```
92-
An example repo doc is here: `https://apollorion.com/flight_repo_named.json`
102+
An example repo doc is here: `https://raw.githubusercontent.com/Apollorion/fly/main/help/example-repo.json`
93103

94104
ex:
95-
1. `fly repo set apollorion https://apollorion.com/flight_repo_named.json`
105+
1. `fly repo set apollorion https://raw.githubusercontent.com/Apollorion/fly/main/help/example-repo.json`
96106
2. `fly repo update`
97107
3. `fly repo unset apollorion`
98108

109+
## Custom flight logic evaluation
110+
Custom flights are a list of strings that are evaluated first come, first served, the extension will evaluate the first logic and if its not suitable it will move to the next.
111+
A suitable flight is defined as; when the number of parameters passed in by the user is 0 and there are no variables left in the logic.
112+
113+
The only exception to this rule is if the last variable in the last defined flight logic has the type `urlencode`, its the only parameter, and no other matches were found.
114+
In this case, the application will url encode all the parameters together and take that flight. You can see this in action with the built in `ghcs` flight.
115+
As long as you pass at least 3 or more parameters to that flight, it will urlencode them together.
116+
99117
Note: Logical flights take precedence over standard flights, in a custom flight repo. If a flight is defined in this extension as a logical flight, and you want to simplify it, you will want to
100118
define it as a logical flight in your repo. Otherwise, your flight will not work.
101119

extension/flights.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,29 @@ import {LogicalFlightDefinition, StandardFlightDefinition} from "./types.js";
33
export const logicalFlights: LogicalFlightDefinition = {
44
"aws": {
55
"override": "https://console.aws.amazon.com/console/home",
6-
"logic": "https://console.aws.amazon.com/console/home?region=${region}",
6+
"logic": ["https://console.aws.amazon.com/console/home?region=${region:plain}"],
77
},
88
"gh": {
9-
"logic": "https://github.com/${gh-org}/${repo}",
9+
"logic": ["https://github.com/${gh-org:plain}/${repo:plain}"],
10+
},
11+
"ghcs": {
12+
"logic": [
13+
"https://cs.github.com/?scopeName=All+repos&scope=&q=org%3A${ghcs-org:plain}+${ghcs-query:urlencode}",
14+
"https://cs.github.com/?scopeName=All+repos&scope=&q=${ghcs-query:urlencode}",
15+
],
1016
},
1117
"tfc": {
1218
"override": "https://app.terraform.io/app/",
13-
"logic": "https://app.terraform.io/app/${tfc-org}/workspaces?search=${tfc-search}"
19+
"logic": ["https://app.terraform.io/app/${tfc-org:plain}/workspaces?search=${tfc-search:plain}"]
20+
},
21+
"dd": {
22+
"override": "https://app.datadoghq.com/",
23+
"logic": ["https://app.datadoghq.com/${dd-product:plain}"]
1424
}
1525
};
1626

1727
export const standardFlights: StandardFlightDefinition = {
18-
"https://app.datadoghq.com/" : ["datadog", "dd"],
19-
20-
"https://github.com/Apollorion/fly/blob/main/help/config-github.md#org-not-set" : ["config-github/org-not-set"],
21-
"https://github.com/Apollorion/fly/blob/main/help/config-github.md#repo-not-set" : ["config-github/repo-not-set"],
22-
"https://github.com/Apollorion/fly/blob/main/help/logical-flights.md#logic-not-found" : ["logical-flights/not-found"]
28+
// This standard flight is included for testing purposes
29+
// normally it would be easier to just use the omnibar for google searches
30+
"https://google.com/": ["google", "go"]
2331
}

extension/helpers.test.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { repoManagement } from './helpers';
22
import {setLocalStorage, getLocalStorage} from "./localstorage.js";
33

4-
function mockGetLocalStorageWithResult() {
4+
function mockGetLocalStorageWithResult(type: string) {
55
// @ts-ignore
66
getLocalStorage = jest.fn(async (key) => {
77
return new Promise((resolve, reject) => {
8-
resolve("{}");
8+
if(type === "good"){
9+
resolve("{}");
10+
} else {
11+
resolve("{\"apollorion\": \"https://apollorion.com/flight_repo.json\"}");
12+
}
913
});
1014
});
1115
}
@@ -25,29 +29,39 @@ function mockSetLocalStorage() {
2529
}
2630

2731
test("repo can set", async () => {
28-
mockGetLocalStorageWithResult();
32+
mockGetLocalStorageWithResult("good");
2933
mockSetLocalStorage();
3034

3135
const result = await repoManagement(["repo", "set", "apollorion", "https://apollorion.com/flight_repo.json"]);
3236
expect(result).toBe("set apollorion https://apollorion.com/flight_repo.json");
3337
});
3438

3539
test("repo can unset", async () => {
36-
mockGetLocalStorageWithResult();
40+
mockGetLocalStorageWithResult("good");
3741
mockSetLocalStorage();
3842

3943
const result = await repoManagement(["repo", "unset", "apollorion"]);
4044
expect(result).toBe("unset apollorion");
4145
});
4246

4347
test("repo can update", async () => {
44-
mockGetLocalStorageWithResult();
48+
mockGetLocalStorageWithResult("good");
4549
mockSetLocalStorage();
4650

4751
const result = await repoManagement(["repo", "update"]);
4852
expect(result).toBe("repos updated");
4953
});
5054

55+
test("repo cannot update", async () => {
56+
mockGetLocalStorageWithResult("bad");
57+
mockSetLocalStorage();
58+
console.log = jest.fn();
59+
60+
const result = await repoManagement(["repo", "update"]);
61+
expect(result).toBe("repos updated");
62+
expect(console.log).toHaveBeenCalledWith('Failed to fetch repo apollorion');
63+
});
64+
5165
test("repo set no local storage", async () => {
5266
mockGetLocalStorageWithoutResult();
5367
mockSetLocalStorage();

extension/helpers.ts

Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
1-
import {Flight, FlightType, LogicalFlightDefinition, StandardFlightDefinition, RepoFlightsResponse} from "./types.js";
1+
import {Flight, FlightType, FlightPlans} from "./types.js";
22
import {logicalFlights, standardFlights} from "./flights.js";
33
import {setLocalStorage, getLocalStorage, unsetLocalStorage} from "./localstorage.js";
44

5-
export async function getFlightFromQuery(query: string[], repoFlightResponse: RepoFlightsResponse | undefined): Promise<Flight> {
5+
const supportedCustomFlightVersion = "2";
6+
7+
export async function getFlightFromQuery(query: string[], flightPlans: FlightPlans): Promise<Flight> {
68
// Remove null items from values
79
query = query.filter(function (el) {
810
return el != null;
911
});
1012

11-
const newFlights = getNewFlightResponseSynchronous(repoFlightResponse);
12-
const newLogicalFlights = newFlights.logical;
13+
const logicalFlightPlans = flightPlans.logical;
1314

14-
if(query[0] in newLogicalFlights){
15+
if(query[0] in logicalFlightPlans){
1516
const identifier = query[0];
1617
query.shift();
1718

1819
let d = undefined;
19-
if(newLogicalFlights[identifier].override !== undefined){
20-
d = newLogicalFlights[identifier].override;
20+
if(logicalFlightPlans[identifier].override !== undefined){
21+
d = logicalFlightPlans[identifier].override;
2122
}
2223

2324
return {
@@ -34,45 +35,33 @@ export async function getFlightFromQuery(query: string[], repoFlightResponse: Re
3435
}
3536
}
3637

37-
export function getNewFlightResponseSynchronous(repoFlightResponse: RepoFlightsResponse | undefined) : RepoFlightsResponse{
38-
let newLogicalFlights: LogicalFlightDefinition;
39-
if(repoFlightResponse !== undefined) {
40-
newLogicalFlights = {
41-
...logicalFlights,
42-
...repoFlightResponse.logical
43-
}
44-
} else {
45-
newLogicalFlights = {
46-
...logicalFlights
47-
}
38+
export async function getFlightPlans(): Promise<FlightPlans>{
39+
let localFlights = undefined;
40+
try {
41+
const jsonString = await getLocalStorage("updated-repos");
42+
localFlights = JSON.parse(jsonString);
43+
} catch (e) {
44+
console.log("No custom repo set", e);
4845
}
4946

50-
let newStandardFlights: StandardFlightDefinition;
51-
if(repoFlightResponse !== undefined) {
52-
newStandardFlights = {
53-
...standardFlights,
54-
...repoFlightResponse.standard
55-
}
56-
} else {
57-
newStandardFlights = {
58-
...standardFlights
47+
if(localFlights === undefined){
48+
return {
49+
logical: logicalFlights,
50+
standard: standardFlights
5951
}
6052
}
6153

62-
return {
63-
logical: newLogicalFlights,
64-
standard: newStandardFlights
54+
if(localFlights.version !== supportedCustomFlightVersion){
55+
console.log("Unsupported local flight version");
56+
return {
57+
logical: logicalFlights,
58+
standard: standardFlights
59+
}
6560
}
6661

67-
}
68-
69-
export async function checkRepoFlights(): Promise<RepoFlightsResponse | undefined>{
70-
try {
71-
const jsonString = await getLocalStorage("updated-repos");
72-
return JSON.parse(jsonString);
73-
} catch (e) {
74-
console.log("No custom repo set", e);
75-
return undefined;
62+
return {
63+
logical: { ...logicalFlights, ...localFlights.logical },
64+
standard: { ...standardFlights, ...localFlights.standard }
7665
}
7766
}
7867

@@ -127,16 +116,18 @@ export async function repoManagement(query: string[]): Promise<string> {
127116
case "update":
128117
try {
129118
repoResult = await getLocalStorage("repos");
119+
console.log(repoResult);
130120
} catch {
131121
repoResult = "{}";
132122
}
133123
repos = JSON.parse(repoResult);
134124

135-
let newRepoContent = {standard: {}, logical: {}};
125+
let newRepoContent = {standard: {}, logical: {}, version: supportedCustomFlightVersion};
136126
for(let name in repos){
137127
try {
138128
let json = JSON.parse(await requestJson(repos[name]));
139-
if(json.version === "1" && Object.keys(json).includes("logical") && Object.keys(json).includes("standard")){
129+
if(Object.keys(json).includes("version") && json.version === supportedCustomFlightVersion
130+
&& Object.keys(json).includes("logical") && Object.keys(json).includes("standard")){
140131
newRepoContent.standard = {...newRepoContent.standard, ...json.standard};
141132
newRepoContent.logical = {...newRepoContent.logical, ...json.logical};
142133
}
@@ -151,6 +142,24 @@ export async function repoManagement(query: string[]): Promise<string> {
151142
}
152143
}
153144

145+
export function makeType(value: string, type: string): string {
146+
switch(type){
147+
case "plain":
148+
return value;
149+
case "urlencode":
150+
return encodeURIComponent(value);
151+
default:
152+
throw new Error("Type not found");
153+
}
154+
}
155+
156+
export function getType(value: string): string {
157+
return value.replace("}", "").split(":")[1]
158+
}
159+
160+
export function getValue(value: string): string {
161+
return value.replace("${", "").split(":")[0]
162+
}
154163

155164
export async function setCalled(query: string[]){
156165
if(query.length === 3){

0 commit comments

Comments
 (0)